mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e320a4b027 | ||
|
|
a534c17e8a | ||
|
|
7bcf469e8c | ||
|
|
64f07ebd33 | ||
|
|
9e0a86212c | ||
|
|
c15a631b9a | ||
|
|
df8fb9369b | ||
|
|
222906ece0 | ||
|
|
ca8aa01c95 | ||
|
|
4cf4fdff30 | ||
|
|
2648f6d100 | ||
|
|
3bc014bc8c | ||
|
|
bb9f5402b3 | ||
|
|
1d3307002c | ||
|
|
37bd952486 | ||
|
|
5c33598fe9 | ||
|
|
54e806a94c | ||
|
|
740b85c6e1 | ||
|
|
8ceb64d16c | ||
|
|
5bf11d4142 | ||
|
|
0229ac775b | ||
|
|
52e3893bf3 | ||
|
|
419afd27b5 | ||
|
|
52550bb7b5 | ||
|
|
d6f3f995cd | ||
|
|
dc6ab6b8d6 | ||
|
|
613f1f9b53 | ||
|
|
eefc72b0c6 | ||
|
|
1ac66bb3e4 | ||
|
|
ac6d6d1a73 | ||
|
|
46f7472bd5 | ||
|
|
d872430b08 | ||
|
|
9d2cf16141 | ||
|
|
8c9fab1aa1 | ||
|
|
e213779235 | ||
|
|
e4bf16b618 | ||
|
|
9c64afa1f6 |
47
.github/workflows/go.yml
vendored
47
.github/workflows/go.yml
vendored
@@ -7,7 +7,9 @@ on:
|
||||
- release-*
|
||||
workflow_dispatch: {}
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
|
||||
env:
|
||||
# Common versions
|
||||
@@ -62,11 +64,50 @@ jobs:
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
|
||||
compatibility-test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Set up Go 1.14
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .work/pkg
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
- name: Install ginkgo
|
||||
run: |
|
||||
sudo apt-get install -y golang-ginkgo-dev
|
||||
|
||||
- name: Setup Kind Cluster
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
version: ${{ env.KIND_VERSION }}
|
||||
|
||||
- name: install Kubebuilder
|
||||
uses: wonderflow/kubebuilder-action@v1.1
|
||||
|
||||
- name: Run Make compatibility-test
|
||||
run: make compatibility-test
|
||||
|
||||
- name: Clean up testdata
|
||||
run: make compatibility-testdata-cleanup
|
||||
|
||||
e2e-tests:
|
||||
runs-on: aliyun
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
@@ -114,6 +155,10 @@ jobs:
|
||||
- name: Run e2e tests
|
||||
run: make e2e-test
|
||||
|
||||
- name: Cleanup image
|
||||
if: ${{ always() }}
|
||||
run: make image-cleanup
|
||||
|
||||
staticcheck:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
|
||||
2
.github/workflows/registry.yml
vendored
2
.github/workflows/registry.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Login docker.io
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -58,20 +58,6 @@ jobs:
|
||||
asset_path: ./_bin/vela-linux-amd64.zip
|
||||
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.zip
|
||||
asset_content_type: binary/octet-stream
|
||||
- name: Upload Linux arm64 tar.gz
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
with:
|
||||
upload_url: ${{ steps.get_release.outputs.upload_url }}
|
||||
asset_path: ./_bin/vela-linux-arm64.tar.gz
|
||||
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-arm64.tar.gz
|
||||
asset_content_type: binary/octet-stream
|
||||
- name: Upload Linux arm64 zip
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
with:
|
||||
upload_url: ${{ steps.get_release.outputs.upload_url }}
|
||||
asset_path: ./_bin/vela-linux-arm64.zip
|
||||
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-arm64.zip
|
||||
asset_content_type: binary/octet-stream
|
||||
- name: Upload MacOS tar.gz
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
with:
|
||||
|
||||
22
Makefile
22
Makefile
@@ -8,7 +8,7 @@ VELA_GITVERSION_VAR := github.com/oam-dev/kubevela/version.GitRevision
|
||||
LDFLAGS ?= "-X $(VELA_VERSION_VAR)=$(VELA_VERSION) -X $(VELA_GITVERSION_VAR)=$(GIT_COMMIT)"
|
||||
|
||||
GOX = go run github.com/mitchellh/gox
|
||||
TARGETS := darwin/amd64 linux/amd64 windows/amd64 linux/arm64
|
||||
TARGETS := darwin/amd64 linux/amd64 windows/amd64
|
||||
DIST_DIRS := find * -type d -exec
|
||||
|
||||
TIME_LONG = `date +%Y-%m-%d' '%H:%M:%S`
|
||||
@@ -125,7 +125,7 @@ docker-push:
|
||||
docker push ${IMG}
|
||||
|
||||
e2e-setup:
|
||||
bin/vela install --set installCertManager=true --image-pull-policy IfNotPresent --image-repo vela-core-test --image-tag $(GIT_COMMIT)
|
||||
bin/vela install --set installCertManager=true --image-pull-policy IfNotPresent --image-repo vela-core-test --image-tag $(GIT_COMMIT) --depend-check-wait 10s
|
||||
ginkgo version
|
||||
ginkgo -v -r e2e/setup
|
||||
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s
|
||||
@@ -144,10 +144,28 @@ e2e-test:
|
||||
CGO_ENABLED=0 go test -timeout 1h -count=1 -v -tags 'integration' ./test/integration
|
||||
@$(OK) tests pass
|
||||
|
||||
compatibility-test: vet lint staticcheck generate-compatibility-testdata
|
||||
# Run compatibility test with old crd
|
||||
COMPATIBILITY_TEST=TRUE go test -race ./pkg/...
|
||||
@$(OK) compatibility-test pass
|
||||
|
||||
generate-compatibility-testdata:
|
||||
mkdir -p ./test/compatibility-test/testdata
|
||||
go run ./test/compatibility-test/convert/main.go ./charts/vela-core/crds ./test/compatibility-test/testdata
|
||||
|
||||
compatibility-testdata-cleanup:
|
||||
rm -f ./test/compatibility-test/testdata/*
|
||||
|
||||
e2e-cleanup:
|
||||
# Clean up
|
||||
rm -rf ~/.vela
|
||||
|
||||
image-cleanup:
|
||||
# Delete Docker image
|
||||
ifneq ($(shell docker images -q vela-core-test:$(GIT_COMMIT)),)
|
||||
docker image rm -f vela-core-test:$(GIT_COMMIT)
|
||||
endif
|
||||
|
||||
# load docker image to the kind cluster
|
||||
kind-load:
|
||||
docker build -t vela-core-test:$(GIT_COMMIT) .
|
||||
|
||||
@@ -24,6 +24,20 @@ import (
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
)
|
||||
|
||||
// CUE defines the encapsulation in CUE format
|
||||
type CUE struct {
|
||||
// Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field.
|
||||
// Template is a required field if CUE is defined in Capability Definition.
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
// Schematic defines the encapsulation of this capability(workload/trait/scope),
|
||||
// the encapsulation can be defined in different ways, e.g. CUE/HCL(terraform)/KUBE(K8s Object)/HELM, etc...
|
||||
type Schematic struct {
|
||||
CUE *CUE `json:"cue,omitempty"`
|
||||
// TODO(wonderflow): support HCL(terraform)/KUBE(K8s Object)/HELM here.
|
||||
}
|
||||
|
||||
// A DefinitionReference refers to a CustomResourceDefinition by name.
|
||||
type DefinitionReference struct {
|
||||
// Name of the referenced CustomResourceDefinition.
|
||||
@@ -73,10 +87,9 @@ type WorkloadDefinitionSpec struct {
|
||||
// +optional
|
||||
Template string `json:"template,omitempty"`
|
||||
|
||||
// TemplateType defines the data format of the template, by default it's CUE format
|
||||
// Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
// Schematic defines the data format and template of the encapsulation of the workload
|
||||
// +optional
|
||||
TemplateType string `json:"templateType,omitempty"`
|
||||
Schematic *Schematic `json:"schematic,omitempty"`
|
||||
|
||||
// Extension is used for extension needs by OAM platform builders
|
||||
// +optional
|
||||
@@ -101,7 +114,7 @@ type Status struct {
|
||||
// is used to validate the schema of the workload when it is embedded in an OAM
|
||||
// Component.
|
||||
// +kubebuilder:printcolumn:JSONPath=".spec.definitionRef.name",name=DEFINITION-NAME,type=string
|
||||
// +kubebuilder:resource:scope=Cluster,categories={crossplane,oam}
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={crossplane,oam}
|
||||
type WorkloadDefinition struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
@@ -150,15 +163,9 @@ type TraitDefinitionSpec struct {
|
||||
// +optional
|
||||
ConflictsWith []string `json:"conflictsWith,omitempty"`
|
||||
|
||||
// Template defines the abstraction template data of the workload, it will replace the old template in extension field.
|
||||
// the data format depends on templateType, by default it's CUE
|
||||
// Schematic defines the data format and template of the encapsulation of the trait
|
||||
// +optional
|
||||
Template string `json:"template,omitempty"`
|
||||
|
||||
// TemplateType defines the data format of the template, by default it's CUE format
|
||||
// Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
// +optional
|
||||
TemplateType string `json:"templateType,omitempty"`
|
||||
Schematic *Schematic `json:"schematic,omitempty"`
|
||||
|
||||
// Status defines the custom health policy and status message for trait
|
||||
// +optional
|
||||
@@ -177,7 +184,7 @@ type TraitDefinitionSpec struct {
|
||||
// to validate the schema of the trait when it is embedded in an OAM
|
||||
// ApplicationConfiguration.
|
||||
// +kubebuilder:printcolumn:JSONPath=".spec.definitionRef.name",name=DEFINITION-NAME,type=string
|
||||
// +kubebuilder:resource:scope=Cluster,categories={crossplane,oam}
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={crossplane,oam}
|
||||
type TraitDefinition struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
@@ -219,7 +226,7 @@ type ScopeDefinitionSpec struct {
|
||||
// to validate the schema of the scope when it is embedded in an OAM
|
||||
// ApplicationConfiguration.
|
||||
// +kubebuilder:printcolumn:JSONPath=".spec.definitionRef.name",name=DEFINITION-NAME,type=string
|
||||
// +kubebuilder:resource:scope=Cluster,categories={crossplane,oam}
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={crossplane,oam}
|
||||
type ScopeDefinition struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
@@ -412,6 +419,16 @@ type WorkloadTrait struct {
|
||||
|
||||
// Message will allow controller to leave some additional information for this trait
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// AppliedGeneration indicates the generation observed by the appConfig controller.
|
||||
// The same field is also recorded in the annotations of traits.
|
||||
// A trait is possible to be deleted from cluster after created.
|
||||
// This field is useful to track the observed generation of traits after they are
|
||||
// deleted.
|
||||
AppliedGeneration int64 `json:"appliedGeneration,omitempty"`
|
||||
|
||||
// DependencyUnsatisfied notify does the trait has dependency unsatisfied
|
||||
DependencyUnsatisfied bool `json:"dependencyUnsatisfied,omitempty"`
|
||||
}
|
||||
|
||||
// A ScopeStatus represents the state of a scope.
|
||||
@@ -439,12 +456,11 @@ type WorkloadStatus struct {
|
||||
// ComponentRevisionName of current component
|
||||
ComponentRevisionName string `json:"componentRevisionName,omitempty"`
|
||||
|
||||
// ObservedGeneration indicates the generation observed by the appconfig controller.
|
||||
// The same field is also recorded in the annotations of workloads.
|
||||
// A workload is possible to be deleted from cluster after created.
|
||||
// This field is useful to track the observed generation of workloads after they are
|
||||
// deleted.
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// DependencyUnsatisfied notify does the workload has dependency unsatisfied
|
||||
DependencyUnsatisfied bool `json:"dependencyUnsatisfied,omitempty"`
|
||||
|
||||
// AppliedComponentRevision indicates the applied component revision name of this workload
|
||||
AppliedComponentRevision string `json:"appliedComponentRevision,omitempty"`
|
||||
|
||||
// Reference to a workload created by an ApplicationConfiguration.
|
||||
Reference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
|
||||
@@ -488,6 +488,21 @@ func (in *CPUResources) DeepCopy() *CPUResources {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CUE) DeepCopyInto(out *CUE) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CUE.
|
||||
func (in *CUE) DeepCopy() *CUE {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CUE)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ChildResourceKind) DeepCopyInto(out *ChildResourceKind) {
|
||||
*out = *in
|
||||
@@ -1556,6 +1571,26 @@ func (in *Revision) DeepCopy() *Revision {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Schematic) DeepCopyInto(out *Schematic) {
|
||||
*out = *in
|
||||
if in.CUE != nil {
|
||||
in, out := &in.CUE, &out.CUE
|
||||
*out = new(CUE)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Schematic.
|
||||
func (in *Schematic) DeepCopy() *Schematic {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Schematic)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScopeDefinition) DeepCopyInto(out *ScopeDefinition) {
|
||||
*out = *in
|
||||
@@ -1767,6 +1802,11 @@ func (in *TraitDefinitionSpec) DeepCopyInto(out *TraitDefinitionSpec) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Schematic != nil {
|
||||
in, out := &in.Schematic, &out.Schematic
|
||||
*out = new(Schematic)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(Status)
|
||||
@@ -1925,6 +1965,11 @@ func (in *WorkloadDefinitionSpec) DeepCopyInto(out *WorkloadDefinitionSpec) {
|
||||
*out = new(Status)
|
||||
**out = **in
|
||||
}
|
||||
if in.Schematic != nil {
|
||||
in, out := &in.Schematic, &out.Schematic
|
||||
*out = new(Schematic)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Extension != nil {
|
||||
in, out := &in.Extension, &out.Extension
|
||||
*out = new(runtime.RawExtension)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:trivialVersions=true output:artifacts:config=../legacy/charts/vela-core-legacy/crds
|
||||
//go:generate go run ../legacy/convert/main.go ../legacy/charts/vela-core-legacy/crds
|
||||
|
||||
//go:generate go run ../hack/crd/update.go ../charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml ../legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml
|
||||
//go:generate go run ../hack/crd/update.go ../charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml
|
||||
|
||||
package apis
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// Protocol defines network protocols supported for things like container ports.
|
||||
type Protocol string
|
||||
|
||||
// TriggerType defines the type of trigger
|
||||
type TriggerType string
|
||||
|
||||
// Autoscaler is the Schema for the autoscalers API
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
type Autoscaler struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec AutoscalerSpec `json:"spec"`
|
||||
Status AutoscalerStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// SetConditions set condition for CR status
|
||||
func (as *Autoscaler) SetConditions(c ...v1alpha1.Condition) {
|
||||
as.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
// GetCondition get condition from CR status
|
||||
func (as *Autoscaler) GetCondition(conditionType v1alpha1.ConditionType) v1alpha1.Condition {
|
||||
return as.Status.GetCondition(conditionType)
|
||||
}
|
||||
|
||||
// GetWorkloadReference get workload reference
|
||||
func (as *Autoscaler) GetWorkloadReference() v1alpha1.TypedReference {
|
||||
return as.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference set workload reference
|
||||
func (as *Autoscaler) SetWorkloadReference(reference v1alpha1.TypedReference) {
|
||||
as.Spec.WorkloadReference = reference
|
||||
}
|
||||
|
||||
// Trigger defines the trigger of Autoscaler
|
||||
type Trigger struct {
|
||||
// Name is the trigger name, if not set, it will be automatically generated and make it globally unique
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
|
||||
Type TriggerType `json:"type"`
|
||||
|
||||
// Condition set the condition when to trigger scaling
|
||||
Condition map[string]string `json:"condition"`
|
||||
}
|
||||
|
||||
// AutoscalerSpec defines the desired state of Autoscaler
|
||||
type AutoscalerSpec struct {
|
||||
// MinReplicas is the minimal replicas
|
||||
// +optional
|
||||
MinReplicas *int32 `json:"minReplicas,omitempty"`
|
||||
|
||||
// MinReplicas is the maximal replicas
|
||||
// +optional
|
||||
MaxReplicas *int32 `json:"maxReplicas,omitempty"`
|
||||
|
||||
// Triggers lists all triggers
|
||||
Triggers []Trigger `json:"triggers"`
|
||||
|
||||
// TargetWorkload specify the workload which is going to be scaled,
|
||||
// it could be WorkloadReference or the child resource of it
|
||||
TargetWorkload TargetWorkload `json:"targetWorkload,omitempty"`
|
||||
|
||||
// WorkloadReference marks the owner of the workload
|
||||
WorkloadReference v1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
}
|
||||
|
||||
// TargetWorkload holds the a reference to the scale target Object
|
||||
type TargetWorkload struct {
|
||||
Name string `json:"name"`
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
}
|
||||
|
||||
// AutoscalerStatus defines the observed state of Autoscaler
|
||||
type AutoscalerStatus struct {
|
||||
v1alpha1.ConditionedStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// AutoscalerList contains a list of Autoscaler
|
||||
type AutoscalerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Autoscaler `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Autoscaler{}, &AutoscalerList{})
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// MetricsTraitSpec defines the desired state of MetricsTrait
|
||||
type MetricsTraitSpec struct {
|
||||
// An endpoint to be monitored by a ServiceMonitor.
|
||||
ScrapeService ScapeServiceEndPoint `json:"scrapeService"`
|
||||
// WorkloadReference to the workload whose metrics needs to be exposed
|
||||
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
}
|
||||
|
||||
// ScapeServiceEndPoint defines a scrapeable endpoint serving Prometheus metrics.
|
||||
type ScapeServiceEndPoint struct {
|
||||
// The format of the metrics data,
|
||||
// The default and only supported format is "prometheus" for now
|
||||
Format string `json:"format,omitempty"`
|
||||
// Number or name of the port to access on the pods targeted by the service.
|
||||
// The default is discovered automatically from podTemplate, metricTrait will create a service for the workload
|
||||
TargetPort intstr.IntOrString `json:"port,omitempty"`
|
||||
// Route service traffic to pods with label keys and values matching this
|
||||
// The default is discovered automatically from podTemplate.
|
||||
// If no podTemplate, use the labels specified here, or use the labels of the workload
|
||||
TargetSelector map[string]string `json:"selector,omitempty"`
|
||||
// HTTP path to scrape for metrics.
|
||||
// default is /metrics
|
||||
// +optional
|
||||
Path string `json:"path,omitempty"`
|
||||
// Scheme at which metrics should be scraped
|
||||
// The default and only supported scheme is "http"
|
||||
// +optional
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
// The default is true
|
||||
// +optional
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
// MetricsTraitStatus defines the observed state of MetricsTrait
|
||||
type MetricsTraitStatus struct {
|
||||
runtimev1alpha1.ConditionedStatus `json:",inline"`
|
||||
|
||||
// ServiceMonitorName managed by this trait
|
||||
ServiceMonitorName string `json:"serviceMonitorName,omitempty"`
|
||||
|
||||
// Port is the real port monitoring
|
||||
Port intstr.IntOrString `json:"port,omitempty"`
|
||||
// SelectorLabels is the real labels selected
|
||||
SelectorLabels map[string]string `json:"selectorLabels,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// MetricsTrait is the Schema for the metricstraits API
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
// +kubebuilder:subresource:status
|
||||
type MetricsTrait struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec MetricsTraitSpec `json:"spec"`
|
||||
Status MetricsTraitStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// MetricsTraitList contains a list of MetricsTrait
|
||||
type MetricsTraitList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []MetricsTrait `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&MetricsTrait{}, &MetricsTraitList{})
|
||||
}
|
||||
|
||||
var _ oam.Trait = &MetricsTrait{}
|
||||
|
||||
// SetConditions for set CR condition
|
||||
func (tr *MetricsTrait) SetConditions(c ...runtimev1alpha1.Condition) {
|
||||
tr.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
// GetCondition for get CR condition
|
||||
func (tr *MetricsTrait) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
|
||||
return tr.Status.GetCondition(c)
|
||||
}
|
||||
|
||||
// GetWorkloadReference of this MetricsTrait.
|
||||
func (tr *MetricsTrait) GetWorkloadReference() runtimev1alpha1.TypedReference {
|
||||
return tr.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference of this MetricsTrait.
|
||||
func (tr *MetricsTrait) SetWorkloadReference(r runtimev1alpha1.TypedReference) {
|
||||
tr.Spec.WorkloadReference = r
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// RouteSpec defines the desired state of Route
|
||||
type RouteSpec struct {
|
||||
// WorkloadReference to the workload whose metrics needs to be exposed
|
||||
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
|
||||
// Host is the host of the route
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS indicate route trait will create SSL secret using cert-manager with specified issuer
|
||||
// If this is nil, route trait will use a selfsigned issuer
|
||||
TLS *TLS `json:"tls,omitempty"`
|
||||
|
||||
// Rules contain multiple rules of route
|
||||
Rules []Rule `json:"rules,omitempty"`
|
||||
|
||||
// Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
Provider string `json:"provider,omitempty"`
|
||||
|
||||
// IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
IngressClass string `json:"ingressClass,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines to route rule
|
||||
type Rule struct {
|
||||
// Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Path is location Path, default for "/"
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
// RewriteTarget will rewrite request from Path to RewriteTarget path.
|
||||
RewriteTarget string `json:"rewriteTarget,omitempty"`
|
||||
|
||||
// CustomHeaders pass a custom list of headers to the backend service.
|
||||
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
|
||||
|
||||
// DefaultBackend will become the ingress default backend if the backend is not available
|
||||
DefaultBackend *runtimev1alpha1.TypedReference `json:"defaultBackend,omitempty"`
|
||||
|
||||
// Backend indicate how to connect backend service
|
||||
// If it's nil, will auto discovery
|
||||
Backend *Backend `json:"backend,omitempty"`
|
||||
}
|
||||
|
||||
// TLS defines certificate issuer and type for mTLS configuration
|
||||
type TLS struct {
|
||||
IssuerName string `json:"issuerName,omitempty"`
|
||||
|
||||
// Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
// +kubebuilder:default:=Issuer
|
||||
Type IssuerType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// IssuerType defines the type of issuer
|
||||
type IssuerType string
|
||||
|
||||
const (
|
||||
// ClusterIssuer is a cluster level type of issuer
|
||||
ClusterIssuer IssuerType = "ClusterIssuer"
|
||||
// NamespaceIssuer is the default one
|
||||
NamespaceIssuer IssuerType = "Issuer"
|
||||
)
|
||||
|
||||
// Backend defines backend configure for route trait.
|
||||
// Route will automatically discover podSpec and label for BackendService.
|
||||
// If BackendService is already set, discovery won't work.
|
||||
// If BackendService is not set, the discovery mechanism will work.
|
||||
type Backend struct {
|
||||
// ReadTimeout used for setting read timeout duration for backend service, the unit is second.
|
||||
ReadTimeout int `json:"readTimeout,omitempty"`
|
||||
// SendTimeout used for setting send timeout duration for backend service, the unit is second.
|
||||
SendTimeout int `json:"sendTimeout,omitempty"`
|
||||
// BackendService specifies the backend K8s service and port, it's optional
|
||||
BackendService *BackendServiceRef `json:"backendService,omitempty"`
|
||||
}
|
||||
|
||||
// BackendServiceRef specifies the backend K8s service and port, if specified, the two fields are all required
|
||||
type BackendServiceRef struct {
|
||||
// Port allow you direct specify backend service port.
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// ServiceName allow you direct specify K8s service for backend service.
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
|
||||
// RouteStatus defines the observed state of Route
|
||||
type RouteStatus struct {
|
||||
Ingresses []runtimev1alpha1.TypedReference `json:"ingresses,omitempty"`
|
||||
Service *runtimev1alpha1.TypedReference `json:"service,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
runtimev1alpha1.ConditionedStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// Route is the Schema for the routes API
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
// +kubebuilder:subresource:status
|
||||
type Route struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec RouteSpec `json:"spec,omitempty"`
|
||||
Status RouteStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// RouteList contains a list of Route
|
||||
// +kubebuilder:object:root=true
|
||||
type RouteList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Route `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Route{}, &RouteList{})
|
||||
}
|
||||
|
||||
var _ oam.Trait = &Route{}
|
||||
|
||||
// SetConditions set condition for CR status
|
||||
func (r *Route) SetConditions(c ...runtimev1alpha1.Condition) {
|
||||
r.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
// GetCondition get condition from CR status
|
||||
func (r *Route) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
|
||||
return r.Status.GetCondition(c)
|
||||
}
|
||||
|
||||
// GetWorkloadReference of this Route Trait.
|
||||
func (r *Route) GetWorkloadReference() runtimev1alpha1.TypedReference {
|
||||
return r.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference of this Route Trait.
|
||||
func (r *Route) SetWorkloadReference(rt runtimev1alpha1.TypedReference) {
|
||||
r.Spec.WorkloadReference = rt
|
||||
}
|
||||
@@ -26,151 +26,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Autoscaler) DeepCopyInto(out *Autoscaler) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Autoscaler.
|
||||
func (in *Autoscaler) DeepCopy() *Autoscaler {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Autoscaler)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Autoscaler) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AutoscalerList) DeepCopyInto(out *AutoscalerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Autoscaler, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerList.
|
||||
func (in *AutoscalerList) DeepCopy() *AutoscalerList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AutoscalerList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *AutoscalerList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AutoscalerSpec) DeepCopyInto(out *AutoscalerSpec) {
|
||||
*out = *in
|
||||
if in.MinReplicas != nil {
|
||||
in, out := &in.MinReplicas, &out.MinReplicas
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.MaxReplicas != nil {
|
||||
in, out := &in.MaxReplicas, &out.MaxReplicas
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.Triggers != nil {
|
||||
in, out := &in.Triggers, &out.Triggers
|
||||
*out = make([]Trigger, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
out.TargetWorkload = in.TargetWorkload
|
||||
out.WorkloadReference = in.WorkloadReference
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerSpec.
|
||||
func (in *AutoscalerSpec) DeepCopy() *AutoscalerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AutoscalerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AutoscalerStatus) DeepCopyInto(out *AutoscalerStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerStatus.
|
||||
func (in *AutoscalerStatus) DeepCopy() *AutoscalerStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AutoscalerStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Backend) DeepCopyInto(out *Backend) {
|
||||
*out = *in
|
||||
if in.BackendService != nil {
|
||||
in, out := &in.BackendService, &out.BackendService
|
||||
*out = new(BackendServiceRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
|
||||
func (in *Backend) DeepCopy() *Backend {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Backend)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackendServiceRef) DeepCopyInto(out *BackendServiceRef) {
|
||||
*out = *in
|
||||
out.Port = in.Port
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendServiceRef.
|
||||
func (in *BackendServiceRef) DeepCopy() *BackendServiceRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackendServiceRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CanaryMetric) DeepCopyInto(out *CanaryMetric) {
|
||||
*out = *in
|
||||
@@ -221,106 +76,6 @@ func (in *MetricsExpectedRange) DeepCopy() *MetricsExpectedRange {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTrait) DeepCopyInto(out *MetricsTrait) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTrait.
|
||||
func (in *MetricsTrait) DeepCopy() *MetricsTrait {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTrait)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MetricsTrait) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTraitList) DeepCopyInto(out *MetricsTraitList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]MetricsTrait, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTraitList.
|
||||
func (in *MetricsTraitList) DeepCopy() *MetricsTraitList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTraitList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MetricsTraitList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTraitSpec) DeepCopyInto(out *MetricsTraitSpec) {
|
||||
*out = *in
|
||||
in.ScrapeService.DeepCopyInto(&out.ScrapeService)
|
||||
out.WorkloadReference = in.WorkloadReference
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTraitSpec.
|
||||
func (in *MetricsTraitSpec) DeepCopy() *MetricsTraitSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTraitSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTraitStatus) DeepCopyInto(out *MetricsTraitStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
out.Port = in.Port
|
||||
if in.SelectorLabels != nil {
|
||||
in, out := &in.SelectorLabels, &out.SelectorLabels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTraitStatus.
|
||||
func (in *MetricsTraitStatus) DeepCopy() *MetricsTraitStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTraitStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSpecWorkload) DeepCopyInto(out *PodSpecWorkload) {
|
||||
*out = *in
|
||||
@@ -677,228 +432,3 @@ func (in *RolloutWebhookPayload) DeepCopy() *RolloutWebhookPayload {
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Route) DeepCopyInto(out *Route) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route.
|
||||
func (in *Route) DeepCopy() *Route {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Route)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Route) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouteList) DeepCopyInto(out *RouteList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Route, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteList.
|
||||
func (in *RouteList) DeepCopy() *RouteList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RouteList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *RouteList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouteSpec) DeepCopyInto(out *RouteSpec) {
|
||||
*out = *in
|
||||
out.WorkloadReference = in.WorkloadReference
|
||||
if in.TLS != nil {
|
||||
in, out := &in.TLS, &out.TLS
|
||||
*out = new(TLS)
|
||||
**out = **in
|
||||
}
|
||||
if in.Rules != nil {
|
||||
in, out := &in.Rules, &out.Rules
|
||||
*out = make([]Rule, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSpec.
|
||||
func (in *RouteSpec) DeepCopy() *RouteSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RouteSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouteStatus) DeepCopyInto(out *RouteStatus) {
|
||||
*out = *in
|
||||
if in.Ingresses != nil {
|
||||
in, out := &in.Ingresses, &out.Ingresses
|
||||
*out = make([]corev1alpha1.TypedReference, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Service != nil {
|
||||
in, out := &in.Service, &out.Service
|
||||
*out = new(corev1alpha1.TypedReference)
|
||||
**out = **in
|
||||
}
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteStatus.
|
||||
func (in *RouteStatus) DeepCopy() *RouteStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RouteStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Rule) DeepCopyInto(out *Rule) {
|
||||
*out = *in
|
||||
if in.CustomHeaders != nil {
|
||||
in, out := &in.CustomHeaders, &out.CustomHeaders
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.DefaultBackend != nil {
|
||||
in, out := &in.DefaultBackend, &out.DefaultBackend
|
||||
*out = new(corev1alpha1.TypedReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.Backend != nil {
|
||||
in, out := &in.Backend, &out.Backend
|
||||
*out = new(Backend)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
|
||||
func (in *Rule) DeepCopy() *Rule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Rule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScapeServiceEndPoint) DeepCopyInto(out *ScapeServiceEndPoint) {
|
||||
*out = *in
|
||||
out.TargetPort = in.TargetPort
|
||||
if in.TargetSelector != nil {
|
||||
in, out := &in.TargetSelector, &out.TargetSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Enabled != nil {
|
||||
in, out := &in.Enabled, &out.Enabled
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScapeServiceEndPoint.
|
||||
func (in *ScapeServiceEndPoint) DeepCopy() *ScapeServiceEndPoint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ScapeServiceEndPoint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLS) DeepCopyInto(out *TLS) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
|
||||
func (in *TLS) DeepCopy() *TLS {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TLS)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TargetWorkload) DeepCopyInto(out *TargetWorkload) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetWorkload.
|
||||
func (in *TargetWorkload) DeepCopy() *TargetWorkload {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TargetWorkload)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Trigger) DeepCopyInto(out *Trigger) {
|
||||
*out = *in
|
||||
if in.Condition != nil {
|
||||
in, out := &in.Condition, &out.Condition
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Trigger.
|
||||
func (in *Trigger) DeepCopy() *Trigger {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Trigger)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -380,16 +380,18 @@ spec:
|
||||
items:
|
||||
description: A WorkloadStatus represents the status of a workload.
|
||||
properties:
|
||||
appliedComponentRevision:
|
||||
description: AppliedComponentRevision indicates the applied component revision name of this workload
|
||||
type: string
|
||||
componentName:
|
||||
description: ComponentName that produced this workload.
|
||||
type: string
|
||||
componentRevisionName:
|
||||
description: ComponentRevisionName of current component
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: ObservedGeneration indicates the generation observed by the appconfig controller. The same field is also recorded in the annotations of workloads. A workload is possible to be deleted from cluster after created. This field is useful to track the observed generation of workloads after they are deleted.
|
||||
format: int64
|
||||
type: integer
|
||||
dependencyUnsatisfied:
|
||||
description: DependencyUnsatisfied notify does the workload has dependency unsatisfied
|
||||
type: boolean
|
||||
scopes:
|
||||
description: Scopes associated with this workload.
|
||||
items:
|
||||
@@ -430,6 +432,13 @@ spec:
|
||||
items:
|
||||
description: A WorkloadTrait represents a trait associated with a workload and its status
|
||||
properties:
|
||||
appliedGeneration:
|
||||
description: AppliedGeneration indicates the generation observed by the appConfig controller. The same field is also recorded in the annotations of traits. A trait is possible to be deleted from cluster after created. This field is useful to track the observed generation of traits after they are deleted.
|
||||
format: int64
|
||||
type: integer
|
||||
dependencyUnsatisfied:
|
||||
description: DependencyUnsatisfied notify does the trait has dependency unsatisfied
|
||||
type: boolean
|
||||
message:
|
||||
description: Message will allow controller to leave some additional information for this trait
|
||||
type: string
|
||||
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
listKind: ScopeDefinitionList
|
||||
plural: scopedefinitions
|
||||
singular: scopedefinition
|
||||
scope: Cluster
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.definitionRef.name
|
||||
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
listKind: TraitDefinitionList
|
||||
plural: traitdefinitions
|
||||
singular: traitdefinition
|
||||
scope: Cluster
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.definitionRef.name
|
||||
@@ -68,6 +68,19 @@ spec:
|
||||
revisionEnabled:
|
||||
description: Revision indicates whether a trait is aware of component revision
|
||||
type: boolean
|
||||
schematic:
|
||||
description: Schematic defines the data format and template of the encapsulation of the trait
|
||||
properties:
|
||||
cue:
|
||||
description: CUE defines the encapsulation in CUE format
|
||||
properties:
|
||||
template:
|
||||
description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition.
|
||||
type: string
|
||||
required:
|
||||
- template
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for trait
|
||||
properties:
|
||||
@@ -78,12 +91,6 @@ spec:
|
||||
description: HealthPolicy defines the health check policy for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object
|
||||
type: string
|
||||
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
listKind: WorkloadDefinitionList
|
||||
plural: workloaddefinitions
|
||||
singular: workloaddefinition
|
||||
scope: Cluster
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.definitionRef.name
|
||||
@@ -82,6 +82,19 @@ spec:
|
||||
revisionLabel:
|
||||
description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods).
|
||||
type: string
|
||||
schematic:
|
||||
description: Schematic defines the data format and template of the encapsulation of the workload
|
||||
properties:
|
||||
cue:
|
||||
description: CUE defines the encapsulation in CUE format
|
||||
properties:
|
||||
template:
|
||||
description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition.
|
||||
type: string
|
||||
required:
|
||||
- template
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for workload
|
||||
properties:
|
||||
@@ -95,9 +108,6 @@ spec:
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
required:
|
||||
- definitionRef
|
||||
type: object
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: autoscalers.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Autoscaler
|
||||
listKind: AutoscalerList
|
||||
plural: autoscalers
|
||||
singular: autoscaler
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Autoscaler is the Schema for the autoscalers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: AutoscalerSpec defines the desired state of Autoscaler
|
||||
properties:
|
||||
maxReplicas:
|
||||
description: MinReplicas is the maximal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
minReplicas:
|
||||
description: MinReplicas is the minimal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
targetWorkload:
|
||||
description: TargetWorkload specify the workload which is going to be scaled, it could be WorkloadReference or the child resource of it
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
triggers:
|
||||
description: Triggers lists all triggers
|
||||
items:
|
||||
description: Trigger defines the trigger of Autoscaler
|
||||
properties:
|
||||
condition:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Condition set the condition when to trigger scaling
|
||||
type: object
|
||||
name:
|
||||
description: Name is the trigger name, if not set, it will be automatically generated and make it globally unique
|
||||
type: string
|
||||
type:
|
||||
description: Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
|
||||
type: string
|
||||
required:
|
||||
- condition
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
workloadRef:
|
||||
description: WorkloadReference marks the owner of the workload
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- triggers
|
||||
type: object
|
||||
status:
|
||||
description: AutoscalerStatus defines the observed state of Autoscaler
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,145 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: metricstraits.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: MetricsTrait
|
||||
listKind: MetricsTraitList
|
||||
plural: metricstraits
|
||||
singular: metricstrait
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: MetricsTrait is the Schema for the metricstraits API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: MetricsTraitSpec defines the desired state of MetricsTrait
|
||||
properties:
|
||||
scrapeService:
|
||||
description: An endpoint to be monitored by a ServiceMonitor.
|
||||
properties:
|
||||
enabled:
|
||||
description: The default is true
|
||||
type: boolean
|
||||
format:
|
||||
description: The format of the metrics data, The default and only supported format is "prometheus" for now
|
||||
type: string
|
||||
path:
|
||||
description: HTTP path to scrape for metrics. default is /metrics
|
||||
type: string
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Number or name of the port to access on the pods targeted by the service. The default is discovered automatically from podTemplate, metricTrait will create a service for the workload
|
||||
x-kubernetes-int-or-string: true
|
||||
scheme:
|
||||
description: Scheme at which metrics should be scraped The default and only supported scheme is "http"
|
||||
type: string
|
||||
selector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Route service traffic to pods with label keys and values matching this The default is discovered automatically from podTemplate. If no podTemplate, use the labels specified here, or use the labels of the workload
|
||||
type: object
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- scrapeService
|
||||
type: object
|
||||
status:
|
||||
description: MetricsTraitStatus defines the observed state of MetricsTrait
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port is the real port monitoring
|
||||
x-kubernetes-int-or-string: true
|
||||
selectorLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: SelectorLabels is the real labels selected
|
||||
type: object
|
||||
serviceMonitorName:
|
||||
description: ServiceMonitorName managed by this trait
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,232 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: routes.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Route
|
||||
listKind: RouteList
|
||||
plural: routes
|
||||
singular: route
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Route is the Schema for the routes API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RouteSpec defines the desired state of Route
|
||||
properties:
|
||||
host:
|
||||
description: Host is the host of the route
|
||||
type: string
|
||||
ingressClass:
|
||||
description: IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
type: string
|
||||
provider:
|
||||
description: Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
type: string
|
||||
rules:
|
||||
description: Rules contain multiple rules of route
|
||||
items:
|
||||
description: Rule defines to route rule
|
||||
properties:
|
||||
backend:
|
||||
description: Backend indicate how to connect backend service If it's nil, will auto discovery
|
||||
properties:
|
||||
backendService:
|
||||
description: BackendService specifies the backend K8s service and port, it's optional
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port allow you direct specify backend service port.
|
||||
x-kubernetes-int-or-string: true
|
||||
serviceName:
|
||||
description: ServiceName allow you direct specify K8s service for backend service.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- serviceName
|
||||
type: object
|
||||
readTimeout:
|
||||
description: ReadTimeout used for setting read timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
sendTimeout:
|
||||
description: SendTimeout used for setting send timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
type: object
|
||||
customHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: CustomHeaders pass a custom list of headers to the backend service.
|
||||
type: object
|
||||
defaultBackend:
|
||||
description: DefaultBackend will become the ingress default backend if the backend is not available
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
|
||||
type: string
|
||||
path:
|
||||
description: Path is location Path, default for "/"
|
||||
type: string
|
||||
rewriteTarget:
|
||||
description: RewriteTarget will rewrite request from Path to RewriteTarget path.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
tls:
|
||||
description: TLS indicate route trait will create SSL secret using cert-manager with specified issuer If this is nil, route trait will use a selfsigned issuer
|
||||
properties:
|
||||
issuerName:
|
||||
type: string
|
||||
type:
|
||||
default: Issuer
|
||||
description: Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
type: string
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: RouteStatus defines the observed state of Route
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
ingresses:
|
||||
items:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
service:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -2,6 +2,7 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: containerizedworkloads.core.oam.dev
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
definitionRef:
|
||||
name: containerizedworkloads.core.oam.dev
|
||||
|
||||
@@ -2,7 +2,7 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ScopeDefinition
|
||||
metadata:
|
||||
name: healthscopes.core.oam.dev
|
||||
namespace: default
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
workloadRefsPath: spec.workloadRefs
|
||||
allowComponentOverlap: true
|
||||
|
||||
@@ -6,6 +6,7 @@ metadata:
|
||||
definition.oam.dev/description: "Configures K8s ingress and service to enable web traffic for your service.
|
||||
Please use route trait in cap center for advanced usage."
|
||||
name: ingress
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
@@ -20,50 +21,52 @@ spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worker
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
selector:
|
||||
"app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
selector:
|
||||
"app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "Configures replicas for your service."
|
||||
name: scaler
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
@@ -12,17 +13,19 @@ spec:
|
||||
definitionRef:
|
||||
name: manualscalertraits.core.oam.dev
|
||||
workloadRefPath: spec.workloadRef
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
//+usage=Replicas of the workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
//+usage=Replicas of the workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
|
||||
|
||||
@@ -3,44 +3,47 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: task
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes jobs that run code or a script to completion."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: jobs.batch
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
template: spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
template: spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
|
||||
|
||||
@@ -3,89 +3,92 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: webservice
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.
|
||||
If workload type is skipped for any service defined in Appfile, it will be defaulted to `webservice` type."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
|
||||
if context["config"] != _|_ {
|
||||
env: context.config
|
||||
}
|
||||
|
||||
ports: [{
|
||||
containerPort: parameter.port
|
||||
}]
|
||||
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits:
|
||||
cpu: parameter.cpu
|
||||
requests:
|
||||
cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
|
||||
// +usage=Which port do you want customer traffic sent to
|
||||
// +short=p
|
||||
port: *80 | int
|
||||
// +usage=Define arguments by using environment variables
|
||||
env?: [...{
|
||||
// +usage=Environment variable name
|
||||
name: string
|
||||
// +usage=The value of the environment variable
|
||||
value?: string
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
|
||||
cpu?: string
|
||||
}
|
||||
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
|
||||
if context["config"] != _|_ {
|
||||
env: context.config
|
||||
}
|
||||
|
||||
ports: [{
|
||||
containerPort: parameter.port
|
||||
}]
|
||||
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits:
|
||||
cpu: parameter.cpu
|
||||
requests:
|
||||
cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
|
||||
// +usage=Which port do you want customer traffic sent to
|
||||
// +short=p
|
||||
port: *80 | int
|
||||
// +usage=Define arguments by using environment variables
|
||||
env?: [...{
|
||||
// +usage=Environment variable name
|
||||
name: string
|
||||
// +usage=The value of the environment variable
|
||||
value?: string
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
|
||||
cpu?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -3,44 +3,47 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,8 @@ spec:
|
||||
{{ if ne .Values.disableCaps "" }}
|
||||
- "--disable-caps={{ .Values.disableCaps }}"
|
||||
{{ end }}
|
||||
- "--concurrent-reconciles={{ .Values.concurrentReconciles }}"
|
||||
- "--depend-check-wait={{ .Values.dependCheckWait }}"
|
||||
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||
imagePullPolicy: {{ quote .Values.image.pullPolicy }}
|
||||
resources:
|
||||
|
||||
@@ -84,3 +84,11 @@ certificate:
|
||||
secretName: webhook-server-cert
|
||||
mountPath: /etc/k8s-webhook-certs
|
||||
caBundle: replace-me
|
||||
|
||||
systemDefinitionNamespace: vela-system
|
||||
|
||||
# concurrentReconciles is the concurrent reconcile number of the controller
|
||||
concurrentReconciles: 4
|
||||
|
||||
# dependCheckWait is the time to wait for ApplicationConfiguration's dependent-resource ready
|
||||
dependCheckWait: 30s
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
injectorv1alpha1 "github.com/oam-dev/trait-injector/api/v1alpha1"
|
||||
injectorcontroller "github.com/oam-dev/trait-injector/controllers"
|
||||
@@ -21,7 +20,6 @@ import (
|
||||
"github.com/oam-dev/trait-injector/pkg/plugin"
|
||||
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
kedav1alpha1 "github.com/wonderflow/keda-api/api/v1alpha1"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
@@ -38,6 +36,7 @@ import (
|
||||
oamcontroller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
oamv1alpha2 "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
oamwebhook "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev"
|
||||
velawebhook "github.com/oam-dev/kubevela/pkg/webhook/standard.oam.dev"
|
||||
@@ -59,11 +58,9 @@ func init() {
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = crdv1.AddToScheme(scheme)
|
||||
_ = oamcore.AddToScheme(scheme)
|
||||
_ = monitoring.AddToScheme(scheme)
|
||||
_ = velacore.AddToScheme(scheme)
|
||||
_ = injectorv1alpha1.AddToScheme(scheme)
|
||||
_ = certmanager.AddToScheme(scheme)
|
||||
_ = kedav1alpha1.AddToScheme(scheme)
|
||||
_ = kruise.AddToScheme(scheme)
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
@@ -103,8 +100,13 @@ func main() {
|
||||
"custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision")
|
||||
flag.StringVar(&disableCaps, "disable-caps", "", "To be disabled builtin capability list.")
|
||||
flag.StringVar(&storageDriver, "storage-driver", driver.LocalDriverName, "Application file save to the storage driver")
|
||||
flag.DurationVar(&syncPeriod, "informer-re-sync-interval", 5*time.Minute,
|
||||
"controller shared informer lister full re-sync period")
|
||||
flag.DurationVar(&syncPeriod, "informer-re-sync-interval", 2*time.Hour, "controller shared informer lister full re-sync period. The default value is 2 hours")
|
||||
flag.StringVar(&oam.SystemDefinitonNamespace, "system-definition-namespace", "vela-system", "define the namespace of the system-level definition")
|
||||
flag.DurationVar(&controllerArgs.LongWait, "long-wait", 1*time.Minute, "long-wait is controller next reconcile interval time like 30s, 2m etc. The default value is 1m,"+
|
||||
" you can set it to 0 for no reconcile routine after success")
|
||||
flag.IntVar(&controllerArgs.ConcurrentReconciles, "concurrent-reconciles", 4, "concurrent-reconciles is the concurrent reconcile number of the controller. The default value is 4")
|
||||
flag.DurationVar(&controllerArgs.DependCheckWait, "depend-check-wait", 30*time.Second, "depend-check-wait is the time to wait for ApplicationConfiguration's dependent-resource ready."+
|
||||
"The default value is 30s, which means if dependent resources were not prepared, the ApplicationConfiguration would be reconciled after 30s.")
|
||||
flag.Parse()
|
||||
|
||||
// setup logging
|
||||
@@ -126,8 +128,12 @@ func main() {
|
||||
|
||||
setupLog.Info(fmt.Sprintf("KubeVela Version: %s, GIT Revision: %s.", version.VelaVersion, version.GitRevision))
|
||||
setupLog.Info(fmt.Sprintf("Disable Capabilities: %s.", disableCaps))
|
||||
setupLog.Info(fmt.Sprintf("core init with definition namespace %s", oam.SystemDefinitonNamespace))
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
restConfig := ctrl.GetConfigOrDie()
|
||||
restConfig.UserAgent = kubevelaName + "/" + version.GitRevision
|
||||
|
||||
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
LeaderElection: enableLeaderElection,
|
||||
|
||||
@@ -12,57 +12,60 @@ spec:
|
||||
isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)
|
||||
customStatus: |-
|
||||
message: "type: " + context.output.spec.template.spec.containers[0].image + ",\t enemies:" + context.outputs.gameconfig.data.enemies
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
envFrom: [{
|
||||
configMapRef: name: context.name + "game-config"
|
||||
}]
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
envFrom: [{
|
||||
configMapRef: name: context.name + "game-config"
|
||||
}]
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputs: gameconfig: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name + "game-config"
|
||||
}
|
||||
data: {
|
||||
enemies: parameter.enemies
|
||||
lives: parameter.lives
|
||||
}
|
||||
}
|
||||
outputs: gameconfig: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name + "game-config"
|
||||
}
|
||||
data: {
|
||||
enemies: parameter.enemies
|
||||
lives: parameter.lives
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
lives: string
|
||||
enemies: string
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
lives: string
|
||||
enemies: string
|
||||
}
|
||||
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
@@ -75,45 +78,47 @@ spec:
|
||||
message: "type: "+ context.outputs.service.spec.type +",\t clusterIP:"+ context.outputs.service.spec.clusterIP+",\t ports:"+ "\(context.outputs.service.spec.ports[0].port)"+",\t domain"+context.outputs.ingress.spec.rules[0].host
|
||||
healthPolicy: |
|
||||
isHealth: len(context.outputs.service.spec.clusterIP) > 0
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector:
|
||||
app: context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector:
|
||||
app: context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
24
design/api/vela-controller-params-reference.md
Normal file
24
design/api/vela-controller-params-reference.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# KubeVela Controller Parameters Reference
|
||||
|
||||
| parameter | type | default | describe |
|
||||
| :-------------------------: | :----: | :-------------------------------: | :----------------------------------------------------------: |
|
||||
| use-webhook | bool | false | Enable Admission Webhook |
|
||||
| use-trait-injector | bool | false | Enable TraitInjector |
|
||||
| webhook-cert-dir | string | /k8s-webhook-server/serving-certs | Admission webhook cert/key dir. |
|
||||
| metrics-addr | string | :8080 | The address the metric endpoint binds to. |
|
||||
| enable-leader-election | bool | false | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. |
|
||||
| leader-election-namespace | string | "" | Determines the namespace in which the leader election configmap will be created. |
|
||||
| log-file-path | string | "" | The file to write logs to. |
|
||||
| log-retain-date | int | 7 | The number of days of logs history to retain. |
|
||||
| log-compress | bool | true | Enable compression on the rotated logs. |
|
||||
| revision-limit | int | 50 | revision-limit is the maximum number of revisions that will be maintained. The default value is 50. |
|
||||
| health-addr | string | :9440 | The address the health endpoint binds to. |
|
||||
| apply-once-only | string | false | For the purpose of some production environment that workload or trait should not be affected if no spec change, available options: on, off, force. |
|
||||
| custom-revision-hook-url | string | "" | custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision |
|
||||
| disable-caps | string | "" | To be disabled builtin capability list. |
|
||||
| storage-driver | string | Local | Application file save to the storage driver |
|
||||
| informer-re-sync-interval | time | 2h | controller shared informer lister full re-sync period |
|
||||
| system-definition-namespace | string | vela-system | define the namespace of the system-level definition |
|
||||
| long-wait | time | 1m | long-wait is controller next reconcile interval time like 30s, 2m etc. The default value is 1m, you can set it to 0 for no reconcile routine after success |
|
||||
| concurrent-reconciles | int | 4 | concurrent-reconciles is the concurrent reconcile number of the controller. |
|
||||
| depend-check-wait | time | 30s | depend-check-wait is the time to wait for ApplicationConfiguration's dependent-resource ready. |
|
||||
@@ -130,8 +130,13 @@ The same mechanism also works for Trait as well as Workload.
|
||||
|
||||
### Apply Once Only Force
|
||||
|
||||
Based on the same mechanism as `apply-once-only`, `apply-once-only-force` allows to skip re-creating a workload or trait that has already been DELETED from the cluster if its spec is not changed.
|
||||
It's regarded as a stronger case of `apply-once-only`.
|
||||
Based on the same mechanism as `apply-once-only`, `apply-once-only-force` has a more strict method for apply only once.
|
||||
|
||||
It allows to skip re-creating a workload or trait that has already been DELETED from the cluster if its spec is not changed.
|
||||
|
||||
Besides the condition in `apply-once-only`, `apply-once-only-force` has one more condition:
|
||||
|
||||
- if the component revision not changed, the workload will not be applied.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
110
docs/en/application.md
Normal file
110
docs/en/application.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Designing Application
|
||||
|
||||
Application encapsulation and abstraction is achieved by the `Application` custom resource.
|
||||
|
||||
## Example
|
||||
|
||||
The sample application below claimed a `backend` component with *Worker* workload type, and a `frontend` component with *Web Service* workload type.
|
||||
|
||||
Moreover, the `frontend` component claimed `sidecar` and `autoscaler` traits which means the workload will be automatically injected with a `fluentd` sidecar and scale from 1-100 replicas triggered by CPU usage.
|
||||
|
||||
> For detailed definition about `Application` *workload type* and *traits*, please read the [core concepts](/en/concepts.md#application) documentation.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: website
|
||||
spec:
|
||||
components:
|
||||
- name: backend
|
||||
type: worker
|
||||
settings:
|
||||
image: busybox
|
||||
cmd:
|
||||
- sleep
|
||||
- '1000'
|
||||
- name: frontend
|
||||
type: webservice
|
||||
settings:
|
||||
image: nginx
|
||||
traits:
|
||||
- name: autoscaler
|
||||
properties:
|
||||
min: 1
|
||||
max: 10
|
||||
cpuPercent: 60
|
||||
- name: sidecar
|
||||
properties:
|
||||
name: "sidecar-test"
|
||||
image: "fluentd"
|
||||
```
|
||||
|
||||
The `type: worker` means the specification of this workload (claimed in following `settings` section) will be enforced by a `WorkloadDefinition` object named `worker` as below:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic."
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
image: string
|
||||
cmd?: [...string]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Hence, the `settings` section of `backend` only supports two parameters: `image` and `cmd`, this is enforced by the `parameter` list of the `.spec.template` field of the definition.
|
||||
|
||||
The similar extensible abstraction mechanism also applies to traits. For example, `name: autoscaler` in `frontend` means its trait specification (i.e. `properties` section) will be enforced by a `TraitDefinition` object named `autoscaler` as below:
|
||||
|
||||
> TBD: a autoscaler TraitDefinition (HPA)
|
||||
|
||||
All the definition objects are expected to be defined and installed by platform team. The end users will only focus on `Application` resource (either render it by tools or author it manually).
|
||||
|
||||
## Conventions and "Standard Contract"
|
||||
|
||||
After the `Application` resource is applied to Kubernetes cluster, the KubeVela runtime will generate and manage the underlying resources instances following below "standard contract" and conventions.
|
||||
|
||||
|
||||
| Label | Description |
|
||||
| :--: | :---------: |
|
||||
|`workload.oam.dev/type=<workload definition name>` | The name of its corresponding `WorkloadDefinition` |
|
||||
|`trait.oam.dev/type=<trait definition name>` | The name of its corresponding `TraitDefinition` |
|
||||
|`app.oam.dev/name=<app name>` | The name of the application it belongs to |
|
||||
|`app.oam.dev/component=<component name>` | The name of the component it belongs to |
|
||||
|`trait.oam.dev/resource=<name of trait resource instance>` | The name of trait resource instance |
|
||||
|
||||
> TBD: the revision names and labels for resource instances are currently work in progress.
|
||||
|
||||
> TBD: a demo for kubectl apply above Application CR and show full detailed underlying resources.
|
||||
383
docs/en/cue/basic.md
Normal file
383
docs/en/cue/basic.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# CUE Basic
|
||||
|
||||
This document will explain how to use [CUE](https://cuelang.org/) as templating module in KubeVela. Please make sure you have already learned about `Application` custom resource and how it leverage templating modules for application encapsulation and abstraction.
|
||||
|
||||
## Why CUE?
|
||||
|
||||
The reasons for KubeVela supports CUE as first class templating solution can be concluded as below:
|
||||
|
||||
- **CUE is designed for large scale configuration.** CUE has the ability to understand a
|
||||
configuration worked on by engineers across a whole company and to safely change a value that modifies thousands of objects in a configuration. This aligns very well with KubeVela's original goal to define and ship production level applications at web scale.
|
||||
- **CUE supports first-class code generation and automation.** CUE can integrate with existing tools and workflows naturally while other tools would have to build complex custom solutions. For example, generate OpenAPI schemas wigh Go code. This is how KubeVela build developer tools and GUI interfaces based on the CUE templates.
|
||||
- **CUE integrates very well with Go.**
|
||||
KubeVela is built with GO just like most projects in Kubernetes system. CUE is also implemented in and exposes a rich API in Go. KubeVela integrates with CUE as its core library and works as a Kubernetes controller. With the help of CUE, KubeVela can easily handle data constraint problems.
|
||||
|
||||
> Pleas also check [The Configuration Complexity Curse](https://blog.cedriccharly.com/post/20191109-the-configuration-complexity-curse/) and [The Logic of CUE](https://cuelang.org/docs/concepts/logic/) for more details.
|
||||
|
||||
## Parameter and Template
|
||||
|
||||
A very simple `WorkloadDefinition` is like below:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: mydeploy
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
}
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": parameter.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": parameter.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `template` field in this definition is a CUE module, it defines two keywords for KubeVela to build the application abstraction:
|
||||
- The `parameter` defines the input parameters from end user, i.e. the configurable fields in the abstraction.
|
||||
- The `output` defines the template for the abstraction.
|
||||
|
||||
## CUE Template Step by Step
|
||||
|
||||
Let's say as the platform team, we only want to allow end user configure `image` and `name` fields in the `Application` abstraction, and automatically generate all rest of the fields. How can we use CUE to achieve this?
|
||||
|
||||
We can start from the final resource we envision the platform will generate based on user inputs, for example:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
meadata:
|
||||
name: mytest # user inputs
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: mytest # user inputs
|
||||
env:
|
||||
- name: a
|
||||
value: b
|
||||
image: nginx:v1 # user inputs
|
||||
metadata:
|
||||
labels:
|
||||
app.oam.dev/component: mytest # generate by user inputs
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: mytest # generate by user inputs
|
||||
```
|
||||
|
||||
Then we can just convert this YAML to JSON and put the whole JSON object into the `output` keyword field:
|
||||
|
||||
```cue
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: "mytest"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": "mytest"
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": "mytest"
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: "mytest"
|
||||
image: "nginx:v1"
|
||||
env: [{name:"a",value:"b"}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since CUE as a superset of JSON, we can use:
|
||||
|
||||
* C style comments,
|
||||
* quotes may be omitted from field names without special characters,
|
||||
* commas at the end of fields are optional,
|
||||
* comma after last element in list is allowed,
|
||||
* outer curly braces are optional.
|
||||
|
||||
After that, we can then add `parameter` keyword, and use it as a variable reference, this is the very basic CUE feature for templating.
|
||||
|
||||
```cue
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
}
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": parameter.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": parameter.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, you can put the above CUE module in the `template` field of `WorkloadDefinition` object and give it a name. Then end users can now author `Application` resource reference this definition as workload type and only have `name` and `image` as configurable parameters.
|
||||
|
||||
## Advanced CUE Templating
|
||||
|
||||
In this section, we will introduce advanced CUE templating features supports in KubeVela.
|
||||
|
||||
### Structural Parameter
|
||||
|
||||
This is the most commonly used feature. It enables us to expose complex data structure for end users. For example, environment variable list.
|
||||
|
||||
A simple guide is as below:
|
||||
|
||||
1. Define a type in the CUE template, it includes a struct (`other`), a string and an integer.
|
||||
|
||||
```
|
||||
#Config: {
|
||||
name: string
|
||||
value: int
|
||||
other: {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. In the `parameter` section, reference above type and define it as `[...#Config]`. Then it can accept inputs from end users as an array list.
|
||||
|
||||
```
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
configSingle: #Config
|
||||
config: [...#Config] # array list parameter
|
||||
}
|
||||
```
|
||||
|
||||
3. In the `output` section, simply do templating as other parameters.
|
||||
|
||||
```
|
||||
output: {
|
||||
...
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
env: parameter.config
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
4. As long as you install a workload definition object (e.g. `mydeploy`) with above template in the system, a new field `config` will be available to use like below:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: website
|
||||
spec:
|
||||
components:
|
||||
- name: backend
|
||||
type: mydeploy
|
||||
settings:
|
||||
image: crccheck/hello-world
|
||||
name: mysvc
|
||||
config: # a complex parameter
|
||||
- name: a
|
||||
value: 1
|
||||
other:
|
||||
key: mykey
|
||||
value: myvalue
|
||||
```
|
||||
|
||||
|
||||
### Conditional Parameter
|
||||
|
||||
Conditional parameter can be used to do `if..else` logic in template.
|
||||
|
||||
Below is an example that when `useENV=true`, it will render env section, otherwise, it will not.
|
||||
|
||||
```
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
useENV: bool
|
||||
}
|
||||
output: {
|
||||
...
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
if parameter.useENV == true {
|
||||
env: [{name: "my-env", value: "my-value"}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Optional and Default Value
|
||||
|
||||
Optional parameter can be skipped, that usually works together with conditional logic.
|
||||
|
||||
Specifically, if some field does not exit, the CUE grammar is `if _variable_ != _|_`, the example is like below:
|
||||
|
||||
```
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
config?: [...#Config]
|
||||
}
|
||||
output: {
|
||||
...
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
if parameter.config != _|_ {
|
||||
config: parameter.config
|
||||
}
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Default Value is marked with a `*` prefix. It's used like
|
||||
|
||||
```
|
||||
parameter: {
|
||||
name: string
|
||||
image: *"nginx:v1" | string
|
||||
port: *80 | int
|
||||
number: *123.4 | float
|
||||
}
|
||||
output: {
|
||||
...
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
So if a parameter field is neither a parameter with default value nor a conditional field, it's a required value.
|
||||
|
||||
### Loop
|
||||
|
||||
#### Loop for Map
|
||||
|
||||
```cue
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
env: [string]: string
|
||||
}
|
||||
output: {
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
env: [
|
||||
for k, v in parameter.env {
|
||||
name: k
|
||||
value: v
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Loop for Slice
|
||||
|
||||
```cue
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
env: [...{name:string,value:string}]
|
||||
}
|
||||
output: {
|
||||
...
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
env: [
|
||||
for _, v in parameter.env {
|
||||
name: v.name
|
||||
value: v.value
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Import CUE Internal Packages
|
||||
|
||||
CUE has many [internal packages](https://pkg.go.dev/cuelang.org/go@v0.2.2/pkg) which also can be used in KubeVela.
|
||||
|
||||
Below is an example that use `strings.Join` to `concat` string list to one string.
|
||||
|
||||
```cue
|
||||
import ("strings")
|
||||
|
||||
parameter: {
|
||||
outputs: [{ip: "1.1.1.1", hostname: "xxx.com"}, {ip: "2.2.2.2", hostname: "yyy.com"}]
|
||||
}
|
||||
output: {
|
||||
spec: {
|
||||
if len(parameter.outputs) > 0 {
|
||||
_x: [ for _, v in parameter.outputs {
|
||||
"\(v.ip) \(v.hostname)"
|
||||
}]
|
||||
message: "Visiting URL: " + strings.Join(_x, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Overall, CUE is a very powerful templating language which could help platform team create extensible application encapsulation and abstraction with ease.
|
||||
221
docs/en/cue/workload-type.md
Normal file
221
docs/en/cue/workload-type.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Defining Workload Types
|
||||
|
||||
In this section, we will introduce more examples of using CUE to define workload types.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The very basic usage of CUE in workload is to extend a Kubernetes resource as a workload type(via `WorkloadDefinition`) and expose configurable parameters to users.
|
||||
|
||||
A Deployment as workload type:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
}
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": parameter.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": parameter.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: parameter.name
|
||||
image: parameter.image
|
||||
}]
|
||||
}}}
|
||||
}
|
||||
```
|
||||
|
||||
A Job as workload type:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: task
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes jobs that run code or a script to completion."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: jobs.batch
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
template: spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
image: parameter.image
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
count: *1 | int
|
||||
image: string
|
||||
restart: *"Never" | string
|
||||
cmd?: [...string]
|
||||
}
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
When you want to reference the runtime instance name for an app, you can use the `conext` keyword to define `parameter`.
|
||||
|
||||
KubeVela runtime provides a `context` struct including app name(`context.appName`) and component name(`context.name`).
|
||||
|
||||
```cue
|
||||
context: {
|
||||
appName: string
|
||||
name: string
|
||||
}
|
||||
```
|
||||
|
||||
Values of the context will be automatically generated before the underlying resources are applied.
|
||||
This is why you can reference the context variable as value in the template.
|
||||
|
||||
```yaml
|
||||
parameter: {
|
||||
image: string
|
||||
}
|
||||
output: {
|
||||
...
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
A workload type can contain multiple Kubernetes resources, for example, we can define a `webserver` workload type that is composed by Deployment and Service.
|
||||
|
||||
Note that in this case, you MUST define the template of component instance in `output` section, and leave all the other templates in `outputs` with resource name claimed. The format MUST be `outputs:<unique-name>:<full template>`.
|
||||
|
||||
> This is how KubeVela know which resource is the running instance of the application component.
|
||||
|
||||
Below is the example:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: webserver
|
||||
annotations:
|
||||
definition.oam.dev/description: "webserver is a combo of Deployment + Service"
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
|
||||
if context["config"] != _|_ {
|
||||
env: context.config
|
||||
}
|
||||
|
||||
ports: [{
|
||||
containerPort: parameter.port
|
||||
}]
|
||||
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits:
|
||||
cpu: parameter.cpu
|
||||
requests:
|
||||
cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// an extra template
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
ports: [
|
||||
{
|
||||
port: parameter.port
|
||||
targetPort: parameter.port
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
image: string
|
||||
cmd?: [...string]
|
||||
port: *80 | int
|
||||
env?: [...{
|
||||
name: string
|
||||
value?: string
|
||||
valueFrom?: {
|
||||
secretKeyRef: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
cpu?: string
|
||||
}
|
||||
```
|
||||
|
||||
> TBD: a generated resource example for above workload definition.
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# Automatically scale workloads by resource utilization metrics and cron
|
||||
|
||||
## Prerequisite
|
||||
Make sure auto-scaler trait controller is installed in your cluster
|
||||
|
||||
Install auto-scaler trait controller with helm
|
||||
|
||||
1. Add helm chart repo for autoscaler trait
|
||||
```shell script
|
||||
helm repo add oam.catalog http://oam.dev/catalog/
|
||||
```
|
||||
|
||||
2. Update the chart repo
|
||||
```shell script
|
||||
helm repo update
|
||||
```
|
||||
|
||||
3. Install autoscaler trait controller
|
||||
```shell script
|
||||
helm install --create-namespace -n vela-system autoscalertrait oam.catalog/autoscalertrait
|
||||
> Note: autoscale is one of the extension capabilities [installed from cap center](../cap-center.md),
|
||||
> please install it if you can't find it in `vela traits`.
|
||||
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
# Monitoring Application
|
||||
|
||||
## Prerequisite
|
||||
Make sure metrics trait controller is installed in your cluster
|
||||
|
||||
Install metrics trait controller with helm
|
||||
|
||||
1. Add helm chart repo for metrics trait
|
||||
```shell script
|
||||
helm repo add oam.catalog http://oam.dev/catalog/
|
||||
```
|
||||
|
||||
2. Update the chart repo
|
||||
```shell script
|
||||
helm repo update
|
||||
```
|
||||
|
||||
3. Install metrics trait controller
|
||||
```shell script
|
||||
helm install --create-namespace -n vela-system metricstrait oam.catalog/metricstrait
|
||||
> Note: metrics is one of the extension capabilities [installed from cap center](../cap-center.md),
|
||||
> please install it if you can't find it in `vela traits`.
|
||||
## Setting metrics policy
|
||||
|
||||
|
||||
If your application has exposed metrics, you can easily tell the platform how to collect the metrics data from your app with `metrics` capability.
|
||||
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
# Setting Routes
|
||||
The `route` section is used to configure the access to your app.
|
||||
## Prerequisite
|
||||
Make sure route trait controller is installed in your cluster
|
||||
|
||||
The `route` section is used to configure the access to your app.
|
||||
Install route trait controller with helm
|
||||
|
||||
1. Add helm chart repo for route trait
|
||||
```shell script
|
||||
helm repo add oam.catalog http://oam.dev/catalog/
|
||||
```
|
||||
|
||||
2. Update the chart repo
|
||||
```shell script
|
||||
helm repo update
|
||||
```
|
||||
|
||||
3. Install route trait controller
|
||||
```shell script
|
||||
helm install --create-namespace -n vela-system routetrait oam.catalog/routetrait
|
||||
> Note: route is one of the extension capabilities [installed from cap center](../cap-center.md),
|
||||
> please install it if you can't find it in `vela traits`.
|
||||
|
||||
|
||||
The `route` section is used to configure the access to your app.
|
||||
|
||||
Add routing config under `express-server`:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -335,30 +335,32 @@ kind: TraitDefinition
|
||||
metadata:
|
||||
name: auth-service
|
||||
spec:
|
||||
template: |
|
||||
parameter: {
|
||||
serviceURL: string
|
||||
}
|
||||
|
||||
processing: {
|
||||
output: {
|
||||
token?: string
|
||||
}
|
||||
# task shall output a json result and output will correlate fields by name.
|
||||
http: {
|
||||
method: *"GET" | string
|
||||
url: parameter.serviceURL
|
||||
request: {
|
||||
body ?: bytes
|
||||
header: {}
|
||||
trailer: {}
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
serviceURL: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patch: {
|
||||
data: token: processing.output.token
|
||||
}
|
||||
processing: {
|
||||
output: {
|
||||
token?: string
|
||||
}
|
||||
// task shall output a json result and output will correlate fields by name.
|
||||
http: {
|
||||
method: *"GET" | string
|
||||
url: parameter.serviceURL
|
||||
request: {
|
||||
body?: bytes
|
||||
header: {}
|
||||
trailer: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patch: {
|
||||
data: token: processing.output.token
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
3
go.mod
3
go.mod
@@ -29,6 +29,7 @@ require (
|
||||
github.com/google/go-github/v32 v32.1.0
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/klauspost/compress v1.10.5 // indirect
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mholt/archiver/v3 v3.3.0
|
||||
@@ -38,7 +39,6 @@ require (
|
||||
github.com/onsi/ginkgo v1.13.0
|
||||
github.com/onsi/gomega v1.10.3
|
||||
github.com/openkruise/kruise-api v0.7.0
|
||||
github.com/openservicemesh/osm v0.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
|
||||
github.com/spf13/cobra v1.1.1
|
||||
@@ -50,7 +50,6 @@ require (
|
||||
github.com/ugorji/go v1.2.1 // indirect
|
||||
github.com/wercker/stern v0.0.0-20190705090245-4fa46dd6987f
|
||||
github.com/wonderflow/cert-manager-api v1.0.3
|
||||
github.com/wonderflow/keda-api v0.0.0-20201026084048-e7c39fa208e8
|
||||
go.uber.org/zap v1.15.0
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 // indirect
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
|
||||
|
||||
@@ -8,14 +8,12 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/oam-dev/kubevela/hack/utils"
|
||||
|
||||
"github.com/openservicemesh/osm/pkg/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Path relative to the Makefile where this is invoked.
|
||||
chartPath := filepath.Join("charts", "vela-core")
|
||||
source, err := cli.GetChartSource(chartPath)
|
||||
source, err := utils.GetChartSource(chartPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error getting chart source:", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -2,7 +2,16 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
helm "helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// FprintZipData converts zip binary contents to a string literal.
|
||||
@@ -28,3 +37,36 @@ func FprintZipData(dest *bytes.Buffer, zipData []byte) {
|
||||
fmt.Fprintf(dest, "\\x%02x", b)
|
||||
}
|
||||
}
|
||||
|
||||
// GetChartSource is a helper to convert a filepath to a chart to a
|
||||
// base64-encoded, gzipped tarball.
|
||||
func GetChartSource(path string) (string, error) {
|
||||
pack := helm.NewPackage()
|
||||
packagedPath, err := pack.Run(path, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Remove(packagedPath)
|
||||
packaged, err := ioutil.ReadFile(packagedPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b64Encoded := bytes.NewBuffer(nil)
|
||||
enc := base64.NewEncoder(base64.StdEncoding, b64Encoded)
|
||||
_, err = io.Copy(enc, bytes.NewReader(packaged))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b64Encoded.String(), nil
|
||||
}
|
||||
|
||||
// LoadChart is a helper to turn a base64-encoded, gzipped tarball into a chart.
|
||||
func LoadChart(source string) (*chart.Chart, error) {
|
||||
dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(source))
|
||||
tgz := bytes.NewBuffer(nil)
|
||||
_, err := io.Copy(tgz, dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loader.LoadArchive(tgz)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
definition.oam.dev/description: "Configures K8s ingress and service to enable web traffic for your service.
|
||||
Please use route trait in cap center for advanced usage."
|
||||
name: ingress
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
@@ -19,4 +20,6 @@ spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worker
|
||||
template: |
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "Configures replicas for your service."
|
||||
name: scaler
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
@@ -11,4 +12,6 @@ spec:
|
||||
definitionRef:
|
||||
name: manualscalertraits.core.oam.dev
|
||||
workloadRefPath: spec.workloadRef
|
||||
template: |
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
|
||||
@@ -2,9 +2,12 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: task
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes jobs that run code or a script to completion."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: jobs.batch
|
||||
template: |
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
|
||||
@@ -2,10 +2,13 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: webservice
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.
|
||||
If workload type is skipped for any service defined in Appfile, it will be defaulted to `webservice` type."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
template: |
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
|
||||
@@ -2,9 +2,12 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
template: |
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
|
||||
@@ -16,7 +16,7 @@ echo "# Code generated by KubeVela templates. DO NOT EDIT." >> tmpC
|
||||
for filename in `ls cue`; do
|
||||
cat "cue/${filename}" > tmp
|
||||
echo "" >> tmp
|
||||
sed -i.bak 's/^/ /' tmp
|
||||
sed -i.bak 's/^/ /' tmp
|
||||
|
||||
nameonly="${filename%.*}"
|
||||
|
||||
|
||||
@@ -380,16 +380,18 @@ spec:
|
||||
items:
|
||||
description: A WorkloadStatus represents the status of a workload.
|
||||
properties:
|
||||
appliedComponentRevision:
|
||||
description: AppliedComponentRevision indicates the applied component revision name of this workload
|
||||
type: string
|
||||
componentName:
|
||||
description: ComponentName that produced this workload.
|
||||
type: string
|
||||
componentRevisionName:
|
||||
description: ComponentRevisionName of current component
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: ObservedGeneration indicates the generation observed by the appconfig controller. The same field is also recorded in the annotations of workloads. A workload is possible to be deleted from cluster after created. This field is useful to track the observed generation of workloads after they are deleted.
|
||||
format: int64
|
||||
type: integer
|
||||
dependencyUnsatisfied:
|
||||
description: DependencyUnsatisfied notify does the workload has dependency unsatisfied
|
||||
type: boolean
|
||||
scopes:
|
||||
description: Scopes associated with this workload.
|
||||
items:
|
||||
@@ -430,6 +432,13 @@ spec:
|
||||
items:
|
||||
description: A WorkloadTrait represents a trait associated with a workload and its status
|
||||
properties:
|
||||
appliedGeneration:
|
||||
description: AppliedGeneration indicates the generation observed by the appConfig controller. The same field is also recorded in the annotations of traits. A trait is possible to be deleted from cluster after created. This field is useful to track the observed generation of traits after they are deleted.
|
||||
format: int64
|
||||
type: integer
|
||||
dependencyUnsatisfied:
|
||||
description: DependencyUnsatisfied notify does the trait has dependency unsatisfied
|
||||
type: boolean
|
||||
message:
|
||||
description: Message will allow controller to leave some additional information for this trait
|
||||
type: string
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
listKind: ScopeDefinitionList
|
||||
plural: scopedefinitions
|
||||
singular: scopedefinition
|
||||
scope: Cluster
|
||||
scope: Namespaced
|
||||
subresources: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
listKind: TraitDefinitionList
|
||||
plural: traitdefinitions
|
||||
singular: traitdefinition
|
||||
scope: Cluster
|
||||
scope: Namespaced
|
||||
subresources: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
@@ -67,6 +67,19 @@ spec:
|
||||
revisionEnabled:
|
||||
description: Revision indicates whether a trait is aware of component revision
|
||||
type: boolean
|
||||
schematic:
|
||||
description: Schematic defines the data format and template of the encapsulation of the trait
|
||||
properties:
|
||||
cue:
|
||||
description: CUE defines the encapsulation in CUE format
|
||||
properties:
|
||||
template:
|
||||
description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition.
|
||||
type: string
|
||||
required:
|
||||
- template
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for trait
|
||||
properties:
|
||||
@@ -77,12 +90,6 @@ spec:
|
||||
description: HealthPolicy defines the health check policy for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object
|
||||
type: string
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
listKind: WorkloadDefinitionList
|
||||
plural: workloaddefinitions
|
||||
singular: workloaddefinition
|
||||
scope: Cluster
|
||||
scope: Namespaced
|
||||
subresources: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
@@ -81,6 +81,19 @@ spec:
|
||||
revisionLabel:
|
||||
description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods).
|
||||
type: string
|
||||
schematic:
|
||||
description: Schematic defines the data format and template of the encapsulation of the workload
|
||||
properties:
|
||||
cue:
|
||||
description: CUE defines the encapsulation in CUE format
|
||||
properties:
|
||||
template:
|
||||
description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition.
|
||||
type: string
|
||||
required:
|
||||
- template
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for workload
|
||||
properties:
|
||||
@@ -94,9 +107,6 @@ spec:
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
required:
|
||||
- definitionRef
|
||||
type: object
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: autoscalers.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Autoscaler
|
||||
listKind: AutoscalerList
|
||||
plural: autoscalers
|
||||
singular: autoscaler
|
||||
scope: Namespaced
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Autoscaler is the Schema for the autoscalers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: AutoscalerSpec defines the desired state of Autoscaler
|
||||
properties:
|
||||
maxReplicas:
|
||||
description: MinReplicas is the maximal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
minReplicas:
|
||||
description: MinReplicas is the minimal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
targetWorkload:
|
||||
description: TargetWorkload specify the workload which is going to be scaled, it could be WorkloadReference or the child resource of it
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
triggers:
|
||||
description: Triggers lists all triggers
|
||||
items:
|
||||
description: Trigger defines the trigger of Autoscaler
|
||||
properties:
|
||||
condition:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Condition set the condition when to trigger scaling
|
||||
type: object
|
||||
name:
|
||||
description: Name is the trigger name, if not set, it will be automatically generated and make it globally unique
|
||||
type: string
|
||||
type:
|
||||
description: Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
|
||||
type: string
|
||||
required:
|
||||
- condition
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
workloadRef:
|
||||
description: WorkloadReference marks the owner of the workload
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- triggers
|
||||
type: object
|
||||
status:
|
||||
description: AutoscalerStatus defines the observed state of Autoscaler
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,146 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: metricstraits.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: MetricsTrait
|
||||
listKind: MetricsTraitList
|
||||
plural: metricstraits
|
||||
singular: metricstrait
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: MetricsTrait is the Schema for the metricstraits API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: MetricsTraitSpec defines the desired state of MetricsTrait
|
||||
properties:
|
||||
scrapeService:
|
||||
description: An endpoint to be monitored by a ServiceMonitor.
|
||||
properties:
|
||||
enabled:
|
||||
description: The default is true
|
||||
type: boolean
|
||||
format:
|
||||
description: The format of the metrics data, The default and only supported format is "prometheus" for now
|
||||
type: string
|
||||
path:
|
||||
description: HTTP path to scrape for metrics. default is /metrics
|
||||
type: string
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Number or name of the port to access on the pods targeted by the service. The default is discovered automatically from podTemplate, metricTrait will create a service for the workload
|
||||
x-kubernetes-int-or-string: true
|
||||
scheme:
|
||||
description: Scheme at which metrics should be scraped The default and only supported scheme is "http"
|
||||
type: string
|
||||
selector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Route service traffic to pods with label keys and values matching this The default is discovered automatically from podTemplate. If no podTemplate, use the labels specified here, or use the labels of the workload
|
||||
type: object
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- scrapeService
|
||||
type: object
|
||||
status:
|
||||
description: MetricsTraitStatus defines the observed state of MetricsTrait
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port is the real port monitoring
|
||||
x-kubernetes-int-or-string: true
|
||||
selectorLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: SelectorLabels is the real labels selected
|
||||
type: object
|
||||
serviceMonitorName:
|
||||
description: ServiceMonitorName managed by this trait
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,232 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: routes.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Route
|
||||
listKind: RouteList
|
||||
plural: routes
|
||||
singular: route
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Route is the Schema for the routes API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RouteSpec defines the desired state of Route
|
||||
properties:
|
||||
host:
|
||||
description: Host is the host of the route
|
||||
type: string
|
||||
ingressClass:
|
||||
description: IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
type: string
|
||||
provider:
|
||||
description: Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
type: string
|
||||
rules:
|
||||
description: Rules contain multiple rules of route
|
||||
items:
|
||||
description: Rule defines to route rule
|
||||
properties:
|
||||
backend:
|
||||
description: Backend indicate how to connect backend service If it's nil, will auto discovery
|
||||
properties:
|
||||
backendService:
|
||||
description: BackendService specifies the backend K8s service and port, it's optional
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port allow you direct specify backend service port.
|
||||
x-kubernetes-int-or-string: true
|
||||
serviceName:
|
||||
description: ServiceName allow you direct specify K8s service for backend service.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- serviceName
|
||||
type: object
|
||||
readTimeout:
|
||||
description: ReadTimeout used for setting read timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
sendTimeout:
|
||||
description: SendTimeout used for setting send timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
type: object
|
||||
customHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: CustomHeaders pass a custom list of headers to the backend service.
|
||||
type: object
|
||||
defaultBackend:
|
||||
description: DefaultBackend will become the ingress default backend if the backend is not available
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
|
||||
type: string
|
||||
path:
|
||||
description: Path is location Path, default for "/"
|
||||
type: string
|
||||
rewriteTarget:
|
||||
description: RewriteTarget will rewrite request from Path to RewriteTarget path.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
tls:
|
||||
description: TLS indicate route trait will create SSL secret using cert-manager with specified issuer If this is nil, route trait will use a selfsigned issuer
|
||||
properties:
|
||||
issuerName:
|
||||
type: string
|
||||
type:
|
||||
description: Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
type: string
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: RouteStatus defines the observed state of Route
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
ingresses:
|
||||
items:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
service:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -34,7 +34,8 @@ func ApplyTerraform(app *v1alpha2.Application, k8sClient client.Client, ioStream
|
||||
var nativeVelaComponents []v1alpha2.ApplicationComponent
|
||||
// parse template
|
||||
appParser := NewApplicationParser(k8sClient, dm)
|
||||
appFile, err := appParser.GenerateAppFile(app.Name, app)
|
||||
// TODO(wangyike) this context only for compiling success, lately mabey surport setting sysNs and appNs in api-server or cli
|
||||
appFile, err := appParser.GenerateAppFile(context.TODO(), app.Name, app)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse appfile: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/pkg/errors"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -130,12 +132,12 @@ func NewApplicationParser(cli client.Client, dm discoverymapper.DiscoveryMapper)
|
||||
}
|
||||
|
||||
// GenerateAppFile converts an application to an Appfile
|
||||
func (p *Parser) GenerateAppFile(name string, app *v1alpha2.Application) (*Appfile, error) {
|
||||
func (p *Parser) GenerateAppFile(ctx context.Context, name string, app *v1alpha2.Application) (*Appfile, error) {
|
||||
appfile := new(Appfile)
|
||||
appfile.Name = name
|
||||
var wds []*Workload
|
||||
for _, comp := range app.Spec.Components {
|
||||
wd, err := p.parseWorkload(comp)
|
||||
wd, err := p.parseWorkload(ctx, comp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -146,12 +148,12 @@ func (p *Parser) GenerateAppFile(name string, app *v1alpha2.Application) (*Appfi
|
||||
return appfile, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, error) {
|
||||
func (p *Parser) parseWorkload(ctx context.Context, comp v1alpha2.ApplicationComponent) (*Workload, error) {
|
||||
workload := new(Workload)
|
||||
workload.Traits = []*Trait{}
|
||||
workload.Name = comp.Name
|
||||
workload.Type = comp.WorkloadType
|
||||
templ, err := util.LoadTemplate(p.client, workload.Type, types.TypeWorkload)
|
||||
templ, err := util.LoadTemplate(ctx, p.client, workload.Type, types.TypeWorkload)
|
||||
if err != nil && !kerrors.IsNotFound(err) {
|
||||
return nil, errors.WithMessagef(err, "fetch type of %s", comp.Name)
|
||||
}
|
||||
@@ -169,7 +171,7 @@ func (p *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, e
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("fail to parse properties of %s for %s", traitValue.Name, comp.Name)
|
||||
}
|
||||
trait, err := p.parseTrait(traitValue.Name, properties)
|
||||
trait, err := p.parseTrait(ctx, traitValue.Name, properties)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "component(%s) parse trait(%s)", comp.Name, traitValue.Name)
|
||||
}
|
||||
@@ -177,7 +179,7 @@ func (p *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, e
|
||||
workload.Traits = append(workload.Traits, trait)
|
||||
}
|
||||
for scopeType, instanceName := range comp.Scopes {
|
||||
gvk, err := util.GetScopeGVK(p.client, p.dm, scopeType)
|
||||
gvk, err := util.GetScopeGVK(ctx, p.client, p.dm, scopeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -189,8 +191,8 @@ func (p *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, e
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseTrait(name string, properties map[string]interface{}) (*Trait, error) {
|
||||
templ, err := util.LoadTemplate(p.client, name, types.TypeTrait)
|
||||
func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}) (*Trait, error) {
|
||||
templ, err := util.LoadTemplate(ctx, p.client, name, types.TypeTrait)
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil, errors.Errorf("trait definition of %s not found", name)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -240,7 +239,7 @@ var _ = Describe("Test application parser", func() {
|
||||
},
|
||||
}
|
||||
|
||||
appfile, err := NewApplicationParser(&tclient, nil).GenerateAppFile("test", &o)
|
||||
appfile, err := NewApplicationParser(&tclient, nil).GenerateAppFile(context.TODO(), "test", &o)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
Expect(equal(expectedExceptApp, appfile)).Should(BeTrue())
|
||||
@@ -426,37 +425,33 @@ var _ = Describe("Test appFile parser", func() {
|
||||
Name: "myweb",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"application.oam.dev": "test"},
|
||||
}, Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"workload.oam.dev/type": "worker",
|
||||
"app.oam.dev/component": "myweb",
|
||||
"app.oam.dev/name": "test",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app.oam.dev/component": "myweb"}},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{"labels": map[string]interface{}{"app.oam.dev/component": "myweb"}},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"command": []interface{}{"sleep", "1000"},
|
||||
"image": "busybox",
|
||||
"name": "myweb",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{"name": "c1", "value": "v1"},
|
||||
map[string]interface{}{"name": "c2", "value": "v2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
expectWorkload := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"workload.oam.dev/type": "worker",
|
||||
"app.oam.dev/component": "myweb",
|
||||
"app.oam.dev/name": "test",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app.oam.dev/component": "myweb"}},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{"labels": map[string]interface{}{"app.oam.dev/component": "myweb"}},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"command": []interface{}{"sleep", "1000"},
|
||||
"image": "busybox",
|
||||
"name": "myweb",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{"name": "c1", "value": "v1"},
|
||||
map[string]interface{}{"name": "c2", "value": "v2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -465,12 +460,30 @@ var _ = Describe("Test appFile parser", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
// assertion util cannot compare slices embedded in map correctly while slice order is not required
|
||||
// e.g., .containers[0].env in this case
|
||||
// as a workaround, prepare two expected targets covering all possible slice order
|
||||
// if any one is satisfied, the equal assertion pass
|
||||
expectWorkloadOptional := expectWorkload.DeepCopy()
|
||||
unstructured.SetNestedSlice(expectWorkloadOptional.Object, []interface{}{
|
||||
map[string]interface{}{
|
||||
"command": []interface{}{"sleep", "1000"},
|
||||
"image": "busybox",
|
||||
"name": "myweb",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{"name": "c2", "value": "v2"},
|
||||
map[string]interface{}{"name": "c1", "value": "v1"},
|
||||
},
|
||||
},
|
||||
}, "spec", "template", "spec", "containers")
|
||||
|
||||
By(" built components' length must be 1")
|
||||
Expect(len(components)).To(BeEquivalentTo(1))
|
||||
Expect(components[0].ObjectMeta).To(BeEquivalentTo(expectComponent.ObjectMeta))
|
||||
Expect(components[0].TypeMeta).To(BeEquivalentTo(expectComponent.TypeMeta))
|
||||
logf.Log.Info(cmp.Diff(components[0].Spec.Workload.Object, expectComponent.Spec.Workload.Object))
|
||||
Expect(assert.ObjectsAreEqual(components[0].Spec.Workload.Object, expectComponent.Spec.Workload.Object)).To(BeTrue())
|
||||
Expect(components[0].Spec.Workload.Object).Should(SatisfyAny(
|
||||
BeEquivalentTo(expectWorkload),
|
||||
BeEquivalentTo(expectWorkloadOptional)))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
type dryRunOptions struct {
|
||||
@@ -52,7 +54,14 @@ func NewDryRunCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
|
||||
|
||||
parser := appfile.NewApplicationParser(newClient, dm)
|
||||
|
||||
appFile, err := parser.GenerateAppFile(app.Name, app)
|
||||
velaEnv, err := GetEnv(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := oamutil.SetNnamespaceInCtx(context.Background(), velaEnv.Namespace)
|
||||
|
||||
appFile, err := parser.GenerateAppFile(ctx, app.Name, app)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "generate appFile")
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/openservicemesh/osm/pkg/cli"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/hack/utils"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/plugins"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
@@ -48,6 +48,7 @@ type chartArgs struct {
|
||||
imageRepo string
|
||||
imageTag string
|
||||
imagePullPolicy string
|
||||
dependCheckWait string
|
||||
more []string
|
||||
}
|
||||
|
||||
@@ -131,6 +132,7 @@ func NewInstallCommand(c types.Args, chartContent string, ioStreams cmdutil.IOSt
|
||||
flag.StringVarP(&i.chartArgs.imagePullPolicy, "image-pull-policy", "", "", "vela core image pull policy, this will align to chart value image.pullPolicy")
|
||||
flag.StringVarP(&i.chartArgs.imageRepo, "image-repo", "", "", "vela core image repo, this will align to chart value image.repo")
|
||||
flag.StringVarP(&i.chartArgs.imageTag, "image-tag", "", "", "vela core image repo, this will align to chart value image.tag")
|
||||
flag.StringVarP(&i.chartArgs.dependCheckWait, "depend-check-wait", "", "", "depend-check-wait, this the time to wait for ApplicationConfiguration's dependent-resource ready")
|
||||
flag.StringVarP(&i.waitReady, "wait", "w", "0s", "wait until vela-core is ready to serve, default will not wait")
|
||||
flag.StringSliceVarP(&i.chartArgs.more, "set", "s", []string{}, "arguments for installing vela-core chart")
|
||||
|
||||
@@ -228,6 +230,9 @@ func (i *initCmd) resolveValues() (map[string]interface{}, error) {
|
||||
if i.chartArgs.imagePullPolicy != "" {
|
||||
valuesConfig = append(valuesConfig, fmt.Sprintf("image.pullPolicy=%s", i.chartArgs.imagePullPolicy))
|
||||
}
|
||||
if i.chartArgs.dependCheckWait != "" {
|
||||
valuesConfig = append(valuesConfig, fmt.Sprintf("dependCheckWait=%s", i.chartArgs.dependCheckWait))
|
||||
}
|
||||
valuesConfig = append(valuesConfig, i.chartArgs.more...)
|
||||
|
||||
for _, val := range valuesConfig {
|
||||
@@ -247,7 +252,7 @@ func InstallOamRuntime(chartPath, chartSource string, vals map[string]interface{
|
||||
ioStreams.Infof("Use customized chart at: %s", chartPath)
|
||||
chartRequested, err = loader.Load(chartPath)
|
||||
} else {
|
||||
chartRequested, err = cli.LoadChart(chartSource)
|
||||
chartRequested, err = utils.LoadChart(chartSource)
|
||||
if chartRequested != nil {
|
||||
m, l := chartRequested.Metadata, len(chartRequested.Raw)
|
||||
ioStreams.Infof("install chart %s, version %s, desc : %s, contains %d file\n", m.Name, m.Version, m.Description, l)
|
||||
|
||||
@@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
package core_oam_dev
|
||||
|
||||
import "time"
|
||||
|
||||
// ApplyOnceOnlyMode enumerates ApplyOnceOnly modes.
|
||||
type ApplyOnceOnlyMode string
|
||||
|
||||
@@ -26,12 +28,12 @@ const (
|
||||
|
||||
// ApplyOnceOnlyOn indicates workloads and traits should not be affected
|
||||
// if no spec change is made in the ApplicationConfiguration.
|
||||
ApplyOnceOnlyOn = "on"
|
||||
ApplyOnceOnlyOn ApplyOnceOnlyMode = "on"
|
||||
|
||||
// ApplyOnceOnlyForce is a more strong case for ApplyOnceOnly, the workload
|
||||
// and traits won't be affected if no spec change is made in the ApplicationConfiguration,
|
||||
// even if the workload or trait has been deleted from cluster.
|
||||
ApplyOnceOnlyForce = "force"
|
||||
ApplyOnceOnlyForce ApplyOnceOnlyMode = "force"
|
||||
)
|
||||
|
||||
// Args args used by controller
|
||||
@@ -47,4 +49,13 @@ type Args struct {
|
||||
// CustomRevisionHookURL is a webhook which will let oam-runtime to call with AC+Component info
|
||||
// The webhook server will return a customized component revision for oam-runtime
|
||||
CustomRevisionHookURL string
|
||||
|
||||
// LongWait is controller next reconcile interval time
|
||||
LongWait time.Duration
|
||||
|
||||
// ConcurrentReconciles is the concurrent reconcile number of the controller
|
||||
ConcurrentReconciles int
|
||||
|
||||
// DependCheckWait is the time to wait for ApplicationConfiguration's dependent-resource ready
|
||||
DependCheckWait time.Duration
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
// RolloutReconcileWaitTime is the time to wait before reconcile again an application still in rollout phase
|
||||
@@ -93,7 +94,9 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
// parse template
|
||||
appParser := appfile.NewApplicationParser(r.Client, r.dm)
|
||||
|
||||
appfile, err := appParser.GenerateAppFile(app.Name, app)
|
||||
ctx = oamutil.SetNnamespaceInCtx(ctx, app.Namespace)
|
||||
|
||||
appfile, err := appParser.GenerateAppFile(ctx, app.Name, app)
|
||||
if err != nil {
|
||||
handler.l.Error(err, "[Handle Parse]")
|
||||
app.Status.SetConditions(errorCondition("Parsed", err))
|
||||
|
||||
@@ -651,7 +651,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
ntd, otd := &v1alpha2.TraitDefinition{}, &v1alpha2.TraitDefinition{}
|
||||
tDDefJson, _ := yaml.YAMLToJSON([]byte(tdDefYamlWithHttp))
|
||||
Expect(json.Unmarshal(tDDefJson, ntd)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "scaler"}, otd)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: ntd.Name, Namespace: ntd.Namespace}, otd)).Should(BeNil())
|
||||
ntd.ResourceVersion = otd.ResourceVersion
|
||||
Expect(k8sClient.Update(ctx, ntd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
@@ -695,13 +695,13 @@ var _ = Describe("Test Application Controller", func() {
|
||||
nwd, owd := &v1alpha2.WorkloadDefinition{}, &v1alpha2.WorkloadDefinition{}
|
||||
wDDefJson, _ := yaml.YAMLToJSON([]byte(wDDefWithHealthYaml))
|
||||
Expect(json.Unmarshal(wDDefJson, nwd)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "worker"}, owd)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: nwd.Name, Namespace: nwd.Namespace}, owd)).Should(BeNil())
|
||||
nwd.ResourceVersion = owd.ResourceVersion
|
||||
Expect(k8sClient.Update(ctx, nwd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
ntd, otd := &v1alpha2.TraitDefinition{}, &v1alpha2.TraitDefinition{}
|
||||
tDDefJson, _ := yaml.YAMLToJSON([]byte(tDDefWithHealthYaml))
|
||||
Expect(json.Unmarshal(tDDefJson, ntd)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "scaler"}, otd)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: ntd.Name, Namespace: ntd.Namespace}, otd)).Should(BeNil())
|
||||
ntd.ResourceVersion = otd.ResourceVersion
|
||||
Expect(k8sClient.Update(ctx, ntd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
compName := "myweb-health"
|
||||
@@ -995,7 +995,7 @@ const (
|
||||
kind: ScopeDefinition
|
||||
metadata:
|
||||
name: healthscopes.core.oam.dev
|
||||
namespace: default
|
||||
namespace: vela-system
|
||||
spec:
|
||||
workloadRefsPath: spec.workloadRefs
|
||||
allowComponentOverlap: true
|
||||
@@ -1007,6 +1007,7 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: vela-system
|
||||
annotations:
|
||||
definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
|
||||
spec:
|
||||
@@ -1066,6 +1067,7 @@ spec:
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: webserver
|
||||
namespace: vela-system
|
||||
annotations:
|
||||
definition.oam.dev/description: "webserver was composed by deployment and service"
|
||||
spec:
|
||||
@@ -1157,6 +1159,7 @@ apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: vela-system
|
||||
annotations:
|
||||
definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
|
||||
spec:
|
||||
@@ -1217,6 +1220,7 @@ spec:
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: nworker
|
||||
namespace: vela-system
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic."
|
||||
spec:
|
||||
@@ -1227,57 +1231,59 @@ spec:
|
||||
isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)
|
||||
customStatus: |-
|
||||
message: "type: " + context.output.spec.template.spec.containers[0].image + ",\t enemies:" + context.outputs.gameconfig.data.enemies
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
envFrom: [{
|
||||
configMapRef: name: context.name + "game-config"
|
||||
}]
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
envFrom: [{
|
||||
configMapRef: name: context.name + "game-config"
|
||||
}]
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputs: gameconfig: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name + "game-config"
|
||||
}
|
||||
data: {
|
||||
enemies: parameter.enemies
|
||||
lives: parameter.lives
|
||||
}
|
||||
}
|
||||
outputs: gameconfig: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name + "game-config"
|
||||
}
|
||||
data: {
|
||||
enemies: parameter.enemies
|
||||
lives: parameter.lives
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
lives: string
|
||||
enemies: string
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
lives: string
|
||||
enemies: string
|
||||
}
|
||||
`
|
||||
tDDefYaml = `
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
@@ -1286,6 +1292,7 @@ metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "Manually scale the app"
|
||||
name: scaler
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
@@ -1315,6 +1322,7 @@ metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "Manually scale the app"
|
||||
name: scaler
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
@@ -1359,6 +1367,7 @@ metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "Manually scale the app"
|
||||
name: scaler
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
@@ -1387,54 +1396,57 @@ spec:
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
name: ingress
|
||||
namespace: vela-system
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
message: "type: "+ context.outputs.service.spec.type +",\t clusterIP:"+ context.outputs.service.spec.clusterIP+",\t ports:"+ "\(context.outputs.service.spec.ports[0].port)"+",\t domain"+context.outputs.ingress.spec.rules[0].host
|
||||
healthPolicy: |
|
||||
isHealth: len(context.outputs.service.spec.clusterIP) > 0
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector:
|
||||
app: context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector:
|
||||
app: context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -17,12 +17,16 @@ limitations under the License.
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -61,9 +65,16 @@ var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
var yamlPath string
|
||||
if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set {
|
||||
yamlPath = "../../../../../test/compatibility-test/testdata"
|
||||
} else {
|
||||
yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds")
|
||||
}
|
||||
logf.Log.Info("start application suit test", "yaml_path", yamlPath)
|
||||
testEnv = &envtest.Environment{
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
CRDDirectoryPaths: []string{filepath.Join("../../../../..", "charts", "vela-core", "crds")},
|
||||
CRDDirectoryPaths: []string{yamlPath},
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -89,6 +100,8 @@ var _ = BeforeSuite(func(done Done) {
|
||||
Scheme: testScheme,
|
||||
dm: dm,
|
||||
}
|
||||
definitonNs := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}}
|
||||
Expect(k8sClient.Create(context.Background(), definitonNs.DeepCopy())).Should(BeNil())
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
|
||||
@@ -31,11 +31,13 @@ import (
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
@@ -50,9 +52,7 @@ import (
|
||||
|
||||
const (
|
||||
reconcileTimeout = 1 * time.Minute
|
||||
dependCheckWait = 10 * time.Second
|
||||
shortWait = 30 * time.Second
|
||||
longWait = 1 * time.Minute
|
||||
)
|
||||
|
||||
var errResult = reconcile.Result{RequeueAfter: shortWait}
|
||||
@@ -92,7 +92,12 @@ func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error {
|
||||
}
|
||||
name := "oam/" + strings.ToLower(v1alpha2.ApplicationConfigurationGroupKind)
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
builder := ctrl.NewControllerManagedBy(mgr)
|
||||
builder.WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: args.ConcurrentReconciles,
|
||||
})
|
||||
|
||||
return builder.
|
||||
Named(name).
|
||||
For(&v1alpha2.ApplicationConfiguration{}).
|
||||
Watches(&source.Kind{Type: &v1alpha2.Component{}}, &ComponentHandler{
|
||||
@@ -102,9 +107,11 @@ func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error {
|
||||
CustomRevisionHookURL: args.CustomRevisionHookURL,
|
||||
}).
|
||||
Complete(NewReconciler(mgr, dm,
|
||||
WithLogger(l.WithValues("controller", name)),
|
||||
l.WithValues("controller", name),
|
||||
WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
|
||||
WithApplyOnceOnlyMode(args.ApplyMode)))
|
||||
WithApplyOnceOnlyMode(args.ApplyMode),
|
||||
WithLongWaitTime(args.LongWait),
|
||||
WithDependCheckWait(args.DependCheckWait)))
|
||||
}
|
||||
|
||||
// An OAMApplicationReconciler reconciles OAM ApplicationConfigurations by rendering and
|
||||
@@ -120,6 +127,8 @@ type OAMApplicationReconciler struct {
|
||||
preHooks map[string]ControllerHooks
|
||||
postHooks map[string]ControllerHooks
|
||||
applyOnceOnlyMode core.ApplyOnceOnlyMode
|
||||
longWait time.Duration
|
||||
dependCheckWait time.Duration
|
||||
}
|
||||
|
||||
// A ReconcilerOption configures a Reconciler.
|
||||
@@ -148,13 +157,6 @@ func WithGarbageCollector(gc GarbageCollector) ReconcilerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger specifies how the Reconciler should log messages.
|
||||
func WithLogger(l logging.Logger) ReconcilerOption {
|
||||
return func(r *OAMApplicationReconciler) {
|
||||
r.log = l
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecorder specifies how the Reconciler should record events.
|
||||
func WithRecorder(er event.Recorder) ReconcilerOption {
|
||||
return func(r *OAMApplicationReconciler) {
|
||||
@@ -184,9 +186,23 @@ func WithApplyOnceOnlyMode(mode core.ApplyOnceOnlyMode) ReconcilerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLongWaitTime set next reconcile time interval
|
||||
func WithLongWaitTime(longWait time.Duration) ReconcilerOption {
|
||||
return func(r *OAMApplicationReconciler) {
|
||||
r.longWait = longWait
|
||||
}
|
||||
}
|
||||
|
||||
// WithDependCheckWait set depend check wait
|
||||
func WithDependCheckWait(dependCheckWait time.Duration) ReconcilerOption {
|
||||
return func(r *OAMApplicationReconciler) {
|
||||
r.dependCheckWait = dependCheckWait
|
||||
}
|
||||
}
|
||||
|
||||
// NewReconciler returns an OAMApplicationReconciler that reconciles ApplicationConfigurations
|
||||
// by rendering and instantiating their Components and Traits.
|
||||
func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, o ...ReconcilerOption) *OAMApplicationReconciler {
|
||||
func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, log logging.Logger, o ...ReconcilerOption) *OAMApplicationReconciler {
|
||||
r := &OAMApplicationReconciler{
|
||||
client: m.GetClient(),
|
||||
scheme: m.GetScheme(),
|
||||
@@ -198,12 +214,12 @@ func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, o ...Reco
|
||||
trait: ResourceRenderFn(renderTrait),
|
||||
},
|
||||
workloads: &workloads{
|
||||
applicator: apply.NewAPIApplicator(m.GetClient()),
|
||||
applicator: apply.NewAPIApplicator(m.GetClient(), log),
|
||||
rawClient: m.GetClient(),
|
||||
dm: dm,
|
||||
},
|
||||
gc: GarbageCollectorFn(eligible),
|
||||
log: logging.NewNopLogger(),
|
||||
log: log,
|
||||
record: event.NewNopRecorder(),
|
||||
preHooks: make(map[string]ControllerHooks),
|
||||
postHooks: make(map[string]ControllerHooks),
|
||||
@@ -236,6 +252,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
}
|
||||
acPatch := ac.DeepCopy()
|
||||
|
||||
ctx = util.SetNnamespaceInCtx(ctx, ac.Namespace)
|
||||
if ac.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
if registerFinalizers(ac) {
|
||||
log.Debug("Register new finalizers", "finalizers", ac.ObjectMeta.Finalizers)
|
||||
@@ -252,6 +269,14 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
|
||||
// make sure this is the last functional defer function to be called
|
||||
defer func() {
|
||||
// Make sure if error occurs, reconcile will not happen too frequency
|
||||
if returnErr != nil {
|
||||
result.RequeueAfter = 0
|
||||
}
|
||||
}()
|
||||
|
||||
// execute the posthooks at the end no matter what
|
||||
defer func() {
|
||||
updateObservedGeneration(ac)
|
||||
@@ -268,11 +293,6 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
r.record.Event(ac, event.Normal(reasonExecutePosthook, "Successfully executed a posthook", "posthook name", name))
|
||||
}
|
||||
returnErr = errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
|
||||
// Make sure if error occurs, reconcile will not happen too frequency
|
||||
if returnErr != nil && result.RequeueAfter < shortWait {
|
||||
result.RequeueAfter = shortWait
|
||||
}
|
||||
}()
|
||||
|
||||
// execute the prehooks
|
||||
@@ -299,7 +319,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
log.Debug("Successfully rendered components", "workloads", len(workloads))
|
||||
r.record.Event(ac, event.Normal(reasonRenderComponents, "Successfully rendered components", "workloads", strconv.Itoa(len(workloads))))
|
||||
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode)}
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode, log)}
|
||||
if err := r.workloads.Apply(ctx, ac.Status.Workloads, workloads, applyOpts...); err != nil {
|
||||
log.Debug("Cannot apply components", "error", err, "requeue-after", time.Now().Add(shortWait))
|
||||
r.record.Event(ac, event.Warning(reasonCannotApplyComponents, err))
|
||||
@@ -320,8 +340,15 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
log := log.WithValues("kind", e.GetKind(), "name", e.GetName())
|
||||
record := r.record.WithAnnotations("kind", e.GetKind(), "name", e.GetName())
|
||||
|
||||
err := r.confirmDeleteOnApplyOnceMode(ctx, ac.GetNamespace(), &e)
|
||||
if err != nil {
|
||||
log.Debug("confirm component can't be garbage collected", "error", err)
|
||||
record.Event(ac, event.Warning(reasonCannotGGComponents, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errGCComponent)))
|
||||
return errResult, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
if err := r.client.Delete(ctx, &e); resource.IgnoreNotFound(err) != nil {
|
||||
log.Debug("Cannot garbage collect component", "error", err, "requeue-after", time.Now().Add(shortWait))
|
||||
log.Debug("Cannot garbage collect component", "error", err)
|
||||
record.Event(ac, event.Warning(reasonCannotGGComponents, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errGCComponent)))
|
||||
return errResult, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
@@ -334,9 +361,9 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
r.updateStatus(ctx, ac, acPatch, workloads)
|
||||
|
||||
ac.Status.Dependency = v1alpha2.DependencyStatus{}
|
||||
waitTime := longWait
|
||||
waitTime := r.longWait
|
||||
if len(depStatus.Unsatisfied) != 0 {
|
||||
waitTime = dependCheckWait
|
||||
waitTime = r.dependCheckWait
|
||||
ac.Status.Dependency = *depStatus
|
||||
}
|
||||
|
||||
@@ -344,6 +371,41 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
return reconcile.Result{RequeueAfter: waitTime}, nil
|
||||
}
|
||||
|
||||
// confirmDeleteOnApplyOnceMode will confirm whether the workload can be delete or not in apply once only enabled mode
|
||||
// currently only workload replicas with 0 can be delete
|
||||
func (r *OAMApplicationReconciler) confirmDeleteOnApplyOnceMode(ctx context.Context, namespace string, u *unstructured.Unstructured) error {
|
||||
if r.applyOnceOnlyMode == core.ApplyOnceOnlyOff {
|
||||
return nil
|
||||
}
|
||||
getU := u.DeepCopy()
|
||||
err := r.client.Get(ctx, client.ObjectKey{Name: u.GetName(), Namespace: namespace}, getU)
|
||||
if err != nil {
|
||||
// no need to check if workload not found
|
||||
return resource.IgnoreNotFound(err)
|
||||
}
|
||||
// only check for workload
|
||||
if labels := getU.GetLabels(); labels == nil || labels[oam.LabelOAMResourceType] != oam.ResourceTypeWorkload {
|
||||
return nil
|
||||
}
|
||||
paved := fieldpath.Pave(getU.Object)
|
||||
|
||||
// TODO: add more kinds of workload replica check here if needed
|
||||
// "spec.replicas" maybe not accurate for all kinds of workload, but it work for most of them(including Deployment/StatefulSet/CloneSet).
|
||||
// For workload which don't align with the `spec.replicas` schema, the check won't work
|
||||
replicas, err := paved.GetInteger("spec.replicas")
|
||||
if err != nil {
|
||||
// it's possible for workload without the `spec.replicas`, it's omitempty
|
||||
if strings.Contains(err.Error(), "no such field") {
|
||||
return nil
|
||||
}
|
||||
return errors.WithMessage(err, "fail to get 'spec.replicas' from workload")
|
||||
}
|
||||
if replicas > 0 {
|
||||
return errors.Errorf("can't delete workload with replicas %d in apply once only mode", replicas)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates v1alpha2.ApplicationConfiguration's Status with retry.RetryOnConflict
|
||||
func (r *OAMApplicationReconciler) UpdateStatus(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, opts ...client.UpdateOption) error {
|
||||
status := ac.DeepCopy().Status
|
||||
@@ -361,7 +423,6 @@ func (r *OAMApplicationReconciler) updateStatus(ctx context.Context, ac, acPatch
|
||||
historyWorkloads := make([]v1alpha2.HistoryWorkload, 0)
|
||||
for i, w := range workloads {
|
||||
ac.Status.Workloads[i] = workloads[i].Status()
|
||||
ac.Status.Workloads[i].ObservedGeneration = ac.GetGeneration()
|
||||
if !w.RevisionEnabled {
|
||||
continue
|
||||
}
|
||||
@@ -398,6 +459,17 @@ func updateObservedGeneration(ac *v1alpha2.ApplicationConfiguration) {
|
||||
if ac.Status.ObservedGeneration != ac.Generation {
|
||||
ac.Status.ObservedGeneration = ac.Generation
|
||||
}
|
||||
for i, w := range ac.Status.Workloads {
|
||||
// only the workload meet all dependency can mean the generation applied successfully
|
||||
if w.AppliedComponentRevision != w.ComponentRevisionName && !w.DependencyUnsatisfied {
|
||||
ac.Status.Workloads[i].AppliedComponentRevision = w.ComponentRevisionName
|
||||
}
|
||||
for j, t := range w.Traits {
|
||||
if t.AppliedGeneration != ac.Generation && !t.DependencyUnsatisfied {
|
||||
ac.Status.Workloads[i].Traits[j].AppliedGeneration = ac.Generation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func patchExtraStatusField(acStatus *v1alpha2.ApplicationConfigurationStatus, acPatchStatus v1alpha2.ApplicationConfigurationStatus) {
|
||||
@@ -485,6 +557,7 @@ func (w Workload) Status() v1alpha2.WorkloadStatus {
|
||||
acw := v1alpha2.WorkloadStatus{
|
||||
ComponentName: w.ComponentName,
|
||||
ComponentRevisionName: w.ComponentRevisionName,
|
||||
DependencyUnsatisfied: w.HasDep,
|
||||
Reference: v1alpha1.TypedReference{
|
||||
APIVersion: w.Workload.GetAPIVersion(),
|
||||
Kind: w.Workload.GetKind(),
|
||||
@@ -502,6 +575,7 @@ func (w Workload) Status() v1alpha2.WorkloadStatus {
|
||||
Kind: w.Traits[i].Object.GetKind(),
|
||||
Name: w.Traits[i].Object.GetName(),
|
||||
}
|
||||
acw.Traits[i].DependencyUnsatisfied = tr.HasDep
|
||||
}
|
||||
for i, s := range w.Scopes {
|
||||
acw.Scopes[i].Reference = v1alpha1.TypedReference{
|
||||
@@ -529,9 +603,20 @@ func (fn GarbageCollectorFn) Eligible(namespace string, ws []v1alpha2.WorkloadSt
|
||||
}
|
||||
|
||||
// IsRevisionWorkload check is a workload is an old revision Workload which shouldn't be garbage collected.
|
||||
// TODO(wonderflow): Do we have a better way to recognise it's a revisionWorkload which can't be garbage collected by AppConfig?
|
||||
func IsRevisionWorkload(status v1alpha2.WorkloadStatus) bool {
|
||||
return strings.HasPrefix(status.Reference.Name, status.ComponentName+"-")
|
||||
func IsRevisionWorkload(status v1alpha2.WorkloadStatus, w []Workload) bool {
|
||||
if strings.HasPrefix(status.Reference.Name, status.ComponentName+"-") {
|
||||
// for compatibility, keep the old way
|
||||
return true
|
||||
}
|
||||
|
||||
// check all workload, with same componentName
|
||||
for _, wr := range w {
|
||||
if wr.ComponentName == status.ComponentName {
|
||||
return wr.RevisionEnabled
|
||||
}
|
||||
}
|
||||
// component not found, should be deleted
|
||||
return false
|
||||
}
|
||||
|
||||
func eligible(namespace string, ws []v1alpha2.WorkloadStatus, w []Workload) []unstructured.Unstructured {
|
||||
@@ -555,7 +640,7 @@ func eligible(namespace string, ws []v1alpha2.WorkloadStatus, w []Workload) []un
|
||||
eligible := make([]unstructured.Unstructured, 0)
|
||||
for _, s := range ws {
|
||||
|
||||
if !applied[s.Reference] && !IsRevisionWorkload(s) {
|
||||
if !applied[s.Reference] && !IsRevisionWorkload(s, w) {
|
||||
w := &unstructured.Unstructured{}
|
||||
w.SetAPIVersion(s.Reference.APIVersion)
|
||||
w.SetKind(s.Reference.Kind)
|
||||
@@ -591,12 +676,11 @@ func (e *GenerationUnchanged) Error() string {
|
||||
|
||||
// applyOnceOnly is an ApplyOption that controls the applying mechanism for workload and trait.
|
||||
// More detail refers to the ApplyOnceOnlyMode type annotation
|
||||
func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnlyMode) apply.ApplyOption {
|
||||
func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnlyMode, log logging.Logger) apply.ApplyOption {
|
||||
return func(_ context.Context, existing, desired runtime.Object) error {
|
||||
if mode == core.ApplyOnceOnlyOff {
|
||||
return nil
|
||||
}
|
||||
|
||||
d, _ := desired.(metav1.Object)
|
||||
if d == nil {
|
||||
return errors.Errorf("cannot access metadata of object being applied: %q",
|
||||
@@ -608,6 +692,7 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl
|
||||
dLabels[oam.LabelOAMResourceType] != oam.ResourceTypeTrait {
|
||||
// this ApplyOption only works for workload and trait
|
||||
// skip if the resource is not workload nor trait, e.g., scope
|
||||
log.Info("ignore apply only once check, because resourceType is not workload or trait", oam.LabelOAMResourceType, dLabels[oam.LabelOAMResourceType])
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -615,41 +700,54 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl
|
||||
if existing == nil {
|
||||
if mode != core.ApplyOnceOnlyForce {
|
||||
// non-force mode will always create the resource if not exist.
|
||||
log.Info("apply only once with mode:" + string(mode) + ", but old resource not exist, will create a new one")
|
||||
return nil
|
||||
}
|
||||
|
||||
createdBefore := false
|
||||
var appliedRevision, appliedGeneration string
|
||||
for _, w := range ac.Status.Workloads {
|
||||
// traverse recorded workloads to find the one matching applied resource
|
||||
if w.Reference.GetObjectKind().GroupVersionKind() == desired.GetObjectKind().GroupVersionKind() &&
|
||||
w.Reference.Name == d.GetName() {
|
||||
// the workload matches applied resource
|
||||
createdBefore = true
|
||||
// for workload, when revision enabled, only when revision changed that can trigger to create a new one
|
||||
if dLabels[oam.LabelOAMResourceType] == oam.ResourceTypeWorkload && w.AppliedComponentRevision == dLabels[oam.LabelAppComponentRevision] {
|
||||
// the revision is not changed, so return an error to abort creating it
|
||||
return &GenerationUnchanged{}
|
||||
}
|
||||
appliedRevision = w.AppliedComponentRevision
|
||||
break
|
||||
}
|
||||
if !createdBefore {
|
||||
// the workload is not matched, then traverse its traits to find matching one
|
||||
for _, t := range w.Traits {
|
||||
if t.Reference.GetObjectKind().GroupVersionKind() == desired.GetObjectKind().GroupVersionKind() &&
|
||||
t.Reference.Name == d.GetName() {
|
||||
// the trait matches applied resource
|
||||
createdBefore = true
|
||||
// the workload is not matched, then traverse its traits to find matching one
|
||||
for _, t := range w.Traits {
|
||||
if t.Reference.GetObjectKind().GroupVersionKind() == desired.GetObjectKind().GroupVersionKind() &&
|
||||
t.Reference.Name == d.GetName() {
|
||||
// the trait matches applied resource
|
||||
createdBefore = true
|
||||
// the resource was created before and appConfig status recorded the resource version applied
|
||||
// if recorded AppliedGeneration and ComponentRevisionName both equal to the applied resource's,
|
||||
// that means its spec is not changed
|
||||
if dLabels[oam.LabelOAMResourceType] == oam.ResourceTypeTrait &&
|
||||
w.ComponentRevisionName == dLabels[oam.LabelAppComponentRevision] &&
|
||||
strconv.FormatInt(t.AppliedGeneration, 10) == dAnnots[oam.AnnotationAppGeneration] {
|
||||
// the revision is not changed, so return an error to abort creating it
|
||||
return &GenerationUnchanged{}
|
||||
}
|
||||
appliedGeneration = strconv.FormatInt(t.AppliedGeneration, 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
// don't use if-else here because it will miss the case that the resource is a trait
|
||||
if createdBefore {
|
||||
// the resource was created before and appconfig status recorded the resource version applied
|
||||
// if recored ObservedGeneration and ComponentRevisionName both equal to the applied resource's,
|
||||
// that means its spec is not changed
|
||||
if (strconv.Itoa(int(w.ObservedGeneration)) != dAnnots[oam.AnnotationAppGeneration]) ||
|
||||
(w.ComponentRevisionName != dLabels[oam.LabelAppComponentRevision]) {
|
||||
// its spec is changed, so re-create the resource
|
||||
return nil
|
||||
}
|
||||
// its spec is not changed, so return an error to abort creating it
|
||||
return &GenerationUnchanged{}
|
||||
}
|
||||
}
|
||||
var message = "apply only once with mode: force, but resource not created before, will create new"
|
||||
if createdBefore {
|
||||
message = "apply only once with mode: force, but resource updated, will create new"
|
||||
}
|
||||
log.Info(message, "appConfig", ac.Name, "gvk", desired.GetObjectKind().GroupVersionKind(), "name", d.GetName(),
|
||||
"resourceType", dLabels[oam.LabelOAMResourceType], "appliedCompRevision", appliedRevision, "labeledCompRevision", dLabels[oam.LabelAppComponentRevision],
|
||||
"appliedGeneration", appliedGeneration, "labeledGeneration", dAnnots[oam.AnnotationAppGeneration])
|
||||
|
||||
// no recorded workloads nor traits matches the applied resource
|
||||
// that means the resource is not created before, so create it
|
||||
return nil
|
||||
@@ -662,10 +760,13 @@ func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnl
|
||||
existing.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
eLabels := e.GetLabels()
|
||||
// if existing reource's (observed)AppConfigGeneration and ComponentRevisionName both equal to the applied one's,
|
||||
// if existing resource's (observed)AppConfigGeneration and ComponentRevisionName both equal to the applied one's,
|
||||
// that means its spec is not changed
|
||||
if (e.GetAnnotations()[oam.AnnotationAppGeneration] != dAnnots[oam.AnnotationAppGeneration]) ||
|
||||
(eLabels[oam.LabelAppComponentRevision] != dLabels[oam.LabelAppComponentRevision]) {
|
||||
log.Info("apply only once with mode: "+string(mode)+", but new generation or revision created, will create new",
|
||||
oam.AnnotationAppGeneration, e.GetAnnotations()[oam.AnnotationAppGeneration]+"/"+dAnnots[oam.AnnotationAppGeneration],
|
||||
oam.LabelAppComponentRevision, eLabels[oam.LabelAppComponentRevision]+"/"+dLabels[oam.LabelAppComponentRevision])
|
||||
// its spec is changed, so apply new configuration to it
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithRenderer(ComponentRenderFn(func(_ context.Context, _ *v1alpha2.ApplicationConfiguration) ([]Workload, *v1alpha2.DependencyStatus, error) {
|
||||
return nil, &v1alpha2.DependencyStatus{}, errBoom
|
||||
})),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -212,6 +213,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithApplicator(WorkloadApplyFns{ApplyFn: func(_ context.Context, _ []v1alpha2.WorkloadStatus, _ []Workload, _ ...apply.ApplyOption) error {
|
||||
return errBoom
|
||||
}}),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -245,6 +247,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithGarbageCollector(GarbageCollectorFn(func(_ string, _ []v1alpha2.WorkloadStatus, _ []Workload) []unstructured.Unstructured {
|
||||
return []unstructured.Unstructured{*workload}
|
||||
})),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -308,10 +311,11 @@ func TestReconciler(t *testing.T) {
|
||||
WithGarbageCollector(GarbageCollectorFn(func(_ string, _ []v1alpha2.WorkloadStatus, _ []Workload) []unstructured.Unstructured {
|
||||
return []unstructured.Unstructured{*trait}
|
||||
})),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{RequeueAfter: dependCheckWait},
|
||||
result: reconcile.Result{RequeueAfter: 10 * time.Second},
|
||||
},
|
||||
},
|
||||
"FailedPreHook": {
|
||||
@@ -352,6 +356,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithPosthook("postHook", ControllerHooksFn(func(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) {
|
||||
return reconcile.Result{RequeueAfter: shortWait}, nil
|
||||
})),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -422,6 +427,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithPosthook("preHookFailed", ControllerHooksFn(func(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) {
|
||||
return reconcile.Result{RequeueAfter: 15 * time.Second}, errBoom
|
||||
})),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -472,6 +478,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithPosthook("preHookFailed", ControllerHooksFn(func(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) {
|
||||
return reconcile.Result{RequeueAfter: 15 * time.Second}, errBoom
|
||||
})),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -539,10 +546,12 @@ func TestReconciler(t *testing.T) {
|
||||
WithPosthook("postHook", ControllerHooksFn(func(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) {
|
||||
return reconcile.Result{RequeueAfter: shortWait}, nil
|
||||
})),
|
||||
WithLongWaitTime(1 * time.Minute),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{RequeueAfter: longWait},
|
||||
result: reconcile.Result{RequeueAfter: 1 * time.Minute},
|
||||
},
|
||||
},
|
||||
"RegisterFinalizer": {
|
||||
@@ -583,6 +592,9 @@ func TestReconciler(t *testing.T) {
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
},
|
||||
},
|
||||
o: []ReconcilerOption{
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{},
|
||||
@@ -615,6 +627,9 @@ func TestReconciler(t *testing.T) {
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
},
|
||||
},
|
||||
o: []ReconcilerOption{
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{},
|
||||
@@ -651,6 +666,7 @@ func TestReconciler(t *testing.T) {
|
||||
WithApplicator(WorkloadApplyFns{FinalizeFn: func(ctx context.Context, ac *v1alpha2.ApplicationConfiguration) error {
|
||||
return errBoom
|
||||
}}),
|
||||
WithDependCheckWait(10 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -661,7 +677,7 @@ func TestReconciler(t *testing.T) {
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewReconciler(tc.args.m, nil, tc.args.o...)
|
||||
r := NewReconciler(tc.args.m, nil, logging.NewNopLogger(), tc.args.o...)
|
||||
got, err := r.Reconcile(reconcile.Request{})
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
@@ -847,9 +863,18 @@ func TestEligible(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsRevisionWorkload(t *testing.T) {
|
||||
if true != IsRevisionWorkload(v1alpha2.WorkloadStatus{ComponentName: "compName", Reference: runtimev1alpha1.TypedReference{Name: "compName-rev1"}}) {
|
||||
if true != IsRevisionWorkload(v1alpha2.WorkloadStatus{ComponentName: "compName", Reference: runtimev1alpha1.TypedReference{Name: "compName-rev1"}}, nil) {
|
||||
t.Error("workloadName has componentName as prefix is revisionWorkload")
|
||||
}
|
||||
if true != IsRevisionWorkload(v1alpha2.WorkloadStatus{ComponentName: "compName", Reference: runtimev1alpha1.TypedReference{Name: "speciedName"}}, []Workload{{ComponentName: "compName", RevisionEnabled: true}}) {
|
||||
t.Error("workloadName has componentName same and revisionEnabled is revisionWorkload")
|
||||
}
|
||||
if false != IsRevisionWorkload(v1alpha2.WorkloadStatus{ComponentName: "compName", Reference: runtimev1alpha1.TypedReference{Name: "speciedName"}}, []Workload{{ComponentName: "compName", RevisionEnabled: false}}) {
|
||||
t.Error("workloadName has componentName same and revisionEnabled is false")
|
||||
}
|
||||
if false != IsRevisionWorkload(v1alpha2.WorkloadStatus{ComponentName: "compName", Reference: runtimev1alpha1.TypedReference{Name: "speciedName"}}, []Workload{{ComponentName: "compName-notmatch", RevisionEnabled: true}}) {
|
||||
t.Error("workload with no prefix and no componentName match is not revisionEnabled ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependency(t *testing.T) {
|
||||
@@ -1628,7 +1653,7 @@ func TestUpdateStatus(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r := NewReconciler(m, nil)
|
||||
r := NewReconciler(m, nil, logging.NewNopLogger())
|
||||
|
||||
ac := &v1alpha2.ApplicationConfiguration{}
|
||||
err := r.client.Get(context.Background(), types.NamespacedName{Name: "example-appconfig"}, ac)
|
||||
|
||||
@@ -136,13 +136,12 @@ spec:
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appName}, ac)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check workload created successfully")
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, workloadKey, &workload)
|
||||
}, 5*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Check reconcile again and no error will happen")
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -223,9 +222,14 @@ spec:
|
||||
By("Check new trait CR is applied")
|
||||
scale := v1alpha2.ManualScalerTrait{}
|
||||
scaleKey := client.ObjectKey{Name: scaleName, Namespace: namespace}
|
||||
err = k8sClient.Get(ctx, scaleKey, &scale)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(scale.Spec.ReplicaCount).Should(Equal(int32(3)))
|
||||
Eventually(func() int32 {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
if err := k8sClient.Get(ctx, scaleKey, &scale); err != nil {
|
||||
return 0
|
||||
}
|
||||
return scale.Spec.ReplicaCount
|
||||
}, 5*time.Second, time.Second).Should(Equal(int32(3)))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
||||
@@ -83,6 +83,9 @@ type workloads struct {
|
||||
}
|
||||
|
||||
func (a *workloads) Apply(ctx context.Context, status []v1alpha2.WorkloadStatus, w []Workload, ao ...apply.ApplyOption) error {
|
||||
if len(w) == 0 {
|
||||
return errors.New("The number of workloads in appConfig is 0 ")
|
||||
}
|
||||
// they are all in the same namespace
|
||||
var namespace = w[0].Workload.GetNamespace()
|
||||
for _, wl := range w {
|
||||
|
||||
@@ -4,6 +4,11 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
@@ -141,8 +146,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
reconcileRetry(reconciler, req)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@@ -166,6 +170,8 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cwObj.Spec.Containers[0].Image).Should(Equal(image1))
|
||||
@@ -267,6 +273,8 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
@@ -285,6 +293,375 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
})
|
||||
|
||||
When("ApplyOnceOnlyForce is enabled", func() {
|
||||
It("tests the situation where workload is not applied at the first because of unsatisfied dependency",
|
||||
func() {
|
||||
componentHandler := &ComponentHandler{Client: k8sClient, RevisionLimit: 100, Logger: logging.NewLogrLogger(ctrl.Log.WithName("component-handler"))}
|
||||
|
||||
By("Enable ApplyOnceOnlyForce")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce
|
||||
|
||||
tempFoo := &unstructured.Unstructured{}
|
||||
tempFoo.SetAPIVersion("example.com/v1")
|
||||
tempFoo.SetKind("Foo")
|
||||
tempFoo.SetNamespace(namespace)
|
||||
|
||||
inName := "data-input"
|
||||
inputWorkload := &unstructured.Unstructured{}
|
||||
inputWorkload.SetAPIVersion("example.com/v1")
|
||||
inputWorkload.SetKind("Foo")
|
||||
inputWorkload.SetNamespace(namespace)
|
||||
inputWorkload.SetName(inName)
|
||||
|
||||
compInName := "comp-in"
|
||||
compIn := v1alpha2.Component{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compInName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: inputWorkload,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
outName := "data-output"
|
||||
outputTrait := tempFoo.DeepCopy()
|
||||
outputTrait.SetName(outName)
|
||||
|
||||
acWithDepName := "ac-dep"
|
||||
acWithDep := v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: acWithDepName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: compInName,
|
||||
DataInputs: []v1alpha2.DataInput{
|
||||
{
|
||||
ValueFrom: v1alpha2.DataInputValueFrom{
|
||||
DataOutputName: "trait-output",
|
||||
},
|
||||
ToFieldPaths: []string{"spec.key"},
|
||||
},
|
||||
},
|
||||
Traits: []v1alpha2.ComponentTrait{{
|
||||
Trait: runtime.RawExtension{Object: outputTrait},
|
||||
DataOutputs: []v1alpha2.DataOutput{{
|
||||
Name: "trait-output",
|
||||
FieldPath: "status.key",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Component")
|
||||
Expect(k8sClient.Create(ctx, &compIn)).Should(Succeed())
|
||||
cmp := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compInName}, cmp)).Should(Succeed())
|
||||
|
||||
cmpV1 := cmp.DeepCopy()
|
||||
By("component handler will automatically create controller revision")
|
||||
Expect(func() bool {
|
||||
_, ok := componentHandler.createControllerRevision(cmpV1, cmpV1)
|
||||
return ok
|
||||
}()).Should(BeTrue())
|
||||
|
||||
By("Creat appConfig & check successfully")
|
||||
Expect(k8sClient.Create(ctx, &acWithDep)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: acWithDepName}, &acWithDep)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile & check successfully")
|
||||
|
||||
reqDep := reconcile.Request{
|
||||
NamespacedName: client.ObjectKey{Namespace: namespace, Name: acWithDepName},
|
||||
}
|
||||
Eventually(func() bool {
|
||||
reconcileRetry(reconciler, reqDep)
|
||||
acWithDep = v1alpha2.ApplicationConfiguration{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: acWithDepName}, &acWithDep); err != nil {
|
||||
return false
|
||||
}
|
||||
return len(acWithDep.Status.Workloads) == 1
|
||||
}, time.Second, 300*time.Millisecond).Should(BeTrue())
|
||||
|
||||
// because dependency is not satisfied so the workload should not be created
|
||||
By("Check the workload is NOT created")
|
||||
workloadIn := tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: inName}, workloadIn)).Should(&util.NotFoundMatcher{})
|
||||
|
||||
// modify the trait to make it satisfy comp's dependency
|
||||
outputTrait = tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: outName}, outputTrait)).Should(Succeed())
|
||||
err := unstructured.SetNestedField(outputTrait.Object, "test", "status", "key")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient.Status().Update(ctx, outputTrait)).Should(Succeed())
|
||||
Eventually(func() string {
|
||||
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: outName}, outputTrait)
|
||||
data, _, _ := unstructured.NestedString(outputTrait.Object, "status", "key")
|
||||
return data
|
||||
}, 3*time.Second, time.Second).Should(Equal("test"))
|
||||
|
||||
By("Reconcile & check ac is satisfied")
|
||||
Eventually(func() []v1alpha2.UnstaifiedDependency {
|
||||
reconcileRetry(reconciler, reqDep)
|
||||
acWithDep = v1alpha2.ApplicationConfiguration{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: acWithDepName}, &acWithDep); err != nil {
|
||||
return []v1alpha2.UnstaifiedDependency{{Reason: err.Error()}}
|
||||
}
|
||||
return acWithDep.Status.Dependency.Unsatisfied
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile & check workload is created")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, reqDep)
|
||||
// the workload is created now because its dependency is satisfied
|
||||
workloadIn := tempFoo.DeepCopy()
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: inName}, workloadIn)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Delete the workload")
|
||||
recreatedWL := tempFoo.DeepCopy()
|
||||
recreatedWL.SetName(inName)
|
||||
Expect(k8sClient.Delete(ctx, recreatedWL)).Should(Succeed())
|
||||
outputTrait = tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: inName}, outputTrait)).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
By("Check workload is not re-created by reconciliation")
|
||||
inputWorkload = tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: inName}, inputWorkload)).Should(util.NotFoundMatcher{})
|
||||
})
|
||||
|
||||
It("tests the situation where workload is not applied at the first because of unsatisfied dependency and revision specified",
|
||||
func() {
|
||||
componentHandler := &ComponentHandler{Client: k8sClient, RevisionLimit: 100, Logger: logging.NewLogrLogger(ctrl.Log.WithName("component-handler"))}
|
||||
|
||||
By("Enable ApplyOnceOnlyForce")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce
|
||||
|
||||
tempFoo := &unstructured.Unstructured{}
|
||||
tempFoo.SetAPIVersion("example.com/v1")
|
||||
tempFoo.SetKind("Foo")
|
||||
tempFoo.SetNamespace(namespace)
|
||||
|
||||
inputWorkload := &unstructured.Unstructured{}
|
||||
inputWorkload.SetAPIVersion("example.com/v1")
|
||||
inputWorkload.SetKind("Foo")
|
||||
inputWorkload.SetNamespace(namespace)
|
||||
|
||||
compInName := "comp-in-revision"
|
||||
compIn := v1alpha2.Component{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compInName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: inputWorkload,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
outName := "data-output"
|
||||
outputTrait := tempFoo.DeepCopy()
|
||||
outputTrait.SetName(outName)
|
||||
|
||||
acWithDepName := "ac-dep"
|
||||
acWithDep := v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: acWithDepName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
RevisionName: compInName + "-v1",
|
||||
DataInputs: []v1alpha2.DataInput{
|
||||
{
|
||||
ValueFrom: v1alpha2.DataInputValueFrom{
|
||||
DataOutputName: "trait-output",
|
||||
},
|
||||
ToFieldPaths: []string{"spec.key"},
|
||||
},
|
||||
},
|
||||
Traits: []v1alpha2.ComponentTrait{{
|
||||
Trait: runtime.RawExtension{Object: outputTrait},
|
||||
DataOutputs: []v1alpha2.DataOutput{{
|
||||
Name: "trait-output",
|
||||
FieldPath: "status.key",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Component")
|
||||
Expect(k8sClient.Create(ctx, &compIn)).Should(Succeed())
|
||||
cmp := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compInName}, cmp)).Should(Succeed())
|
||||
|
||||
cmpV1 := cmp.DeepCopy()
|
||||
By("component handler will automatically create controller revision")
|
||||
Expect(func() bool {
|
||||
_, ok := componentHandler.createControllerRevision(cmpV1, cmpV1)
|
||||
return ok
|
||||
}()).Should(BeTrue())
|
||||
|
||||
By("Creat appConfig & check successfully")
|
||||
Expect(k8sClient.Create(ctx, &acWithDep)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: acWithDepName}, &acWithDep)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile & check successfully")
|
||||
|
||||
reqDep := reconcile.Request{
|
||||
NamespacedName: client.ObjectKey{Namespace: namespace, Name: acWithDepName},
|
||||
}
|
||||
Eventually(func() bool {
|
||||
reconcileRetry(reconciler, reqDep)
|
||||
acWithDep = v1alpha2.ApplicationConfiguration{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: acWithDepName}, &acWithDep); err != nil {
|
||||
return false
|
||||
}
|
||||
return len(acWithDep.Status.Workloads) == 1
|
||||
}, time.Second, 300*time.Millisecond).Should(BeTrue())
|
||||
|
||||
// because dependency is not satisfied so the workload should not be created
|
||||
By("Check the workload is NOT created")
|
||||
workloadIn := tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compInName + "-v1"}, workloadIn)).Should(&util.NotFoundMatcher{})
|
||||
|
||||
// modify the trait to make it satisfy comp's dependency
|
||||
outputTrait = tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: outName}, outputTrait)).Should(Succeed())
|
||||
err := unstructured.SetNestedField(outputTrait.Object, "test", "status", "key")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient.Status().Update(ctx, outputTrait)).Should(Succeed())
|
||||
Eventually(func() string {
|
||||
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: outName}, outputTrait)
|
||||
data, _, _ := unstructured.NestedString(outputTrait.Object, "status", "key")
|
||||
return data
|
||||
}, 3*time.Second, time.Second).Should(Equal("test"))
|
||||
|
||||
By("Reconcile & check ac is satisfied")
|
||||
Eventually(func() []v1alpha2.UnstaifiedDependency {
|
||||
reconcileRetry(reconciler, reqDep)
|
||||
acWithDep = v1alpha2.ApplicationConfiguration{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: acWithDepName}, &acWithDep); err != nil {
|
||||
return []v1alpha2.UnstaifiedDependency{{Reason: err.Error()}}
|
||||
}
|
||||
return acWithDep.Status.Dependency.Unsatisfied
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile & check workload is created")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, reqDep)
|
||||
// the workload should be created now because its dependency is satisfied
|
||||
workloadIn := tempFoo.DeepCopy()
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compInName}, workloadIn)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Delete the workload")
|
||||
recreatedWL := tempFoo.DeepCopy()
|
||||
recreatedWL.SetName(compInName)
|
||||
Expect(k8sClient.Delete(ctx, recreatedWL)).Should(Succeed())
|
||||
inputWorkload2 := tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compInName}, inputWorkload2)).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
By("Check workload is not re-created by reconciliation")
|
||||
inputWorkload = tempFoo.DeepCopy()
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compInName}, inputWorkload)).Should(util.NotFoundMatcher{})
|
||||
})
|
||||
|
||||
It("should normally create workload/trait resources at fist time", func() {
|
||||
By("Enable ApplyOnceOnlyForce")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce
|
||||
component2 := v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
Kind: "Component",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycomp2",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: &cw,
|
||||
},
|
||||
},
|
||||
}
|
||||
newFakeTrait := fakeTrait.DeepCopy()
|
||||
newFakeTrait.SetName("mytrait2")
|
||||
appConfig2 := v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "myac2",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: "mycomp2",
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{Trait: runtime.RawExtension{Object: newFakeTrait}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Component")
|
||||
Expect(k8sClient.Create(ctx, &component2)).Should(Succeed())
|
||||
time.Sleep(time.Second)
|
||||
|
||||
By("Creat appConfig & check successfully")
|
||||
Expect(k8sClient.Create(ctx, &appConfig2)).Should(Succeed())
|
||||
time.Sleep(time.Second)
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error {
|
||||
_, err := reconciler.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Name: "myac2", Namespace: namespace}})
|
||||
return err
|
||||
}()).Should(BeNil())
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: "mycomp2"}, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cwObj.Spec.Containers[0].Image).Should(Equal(image1))
|
||||
|
||||
By("Get trait instance & Check trait spec")
|
||||
fooObj := &unstructured.Unstructured{}
|
||||
fooObj.SetAPIVersion("example.com/v1")
|
||||
fooObj.SetKind("Foo")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: "mytrait2"}, fooObj)
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
fooObjV, _, _ := unstructured.NestedString(fooObj.Object, "spec", "key")
|
||||
Expect(fooObjV).Should(Equal(traitSpecValue1))
|
||||
|
||||
})
|
||||
|
||||
It("should not revert changes of workload/trait made by others", func() {
|
||||
By("Enable ApplyOnceOnlyForce")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce
|
||||
@@ -292,6 +669,8 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cwObj.Spec.Containers[0].Image).Should(Equal(image1))
|
||||
@@ -393,8 +772,10 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
By("Get workload instance")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Get trait instance & Check trait spec")
|
||||
fooObj := unstructured.Unstructured{}
|
||||
@@ -426,7 +807,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
recreatedFooObj.SetKind("Foo")
|
||||
Expect(k8sClient.Get(ctx, traitObjKey, &recreatedFooObj)).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Update Appconfig to trigger generation augment")
|
||||
By("Update AppConfig to trigger generation updated")
|
||||
unstructured.SetNestedField(fakeTrait.Object, "newvalue", "spec", "key")
|
||||
appConfig = v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -445,14 +826,24 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Patch(ctx, &appConfig, client.Merge)).Should(Succeed())
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
time.Sleep(3 * time.Second)
|
||||
By("Check AppConfig is updated successfully")
|
||||
updateAC := v1alpha2.ApplicationConfiguration{}
|
||||
Eventually(func() int64 {
|
||||
if err := k8sClient.Get(ctx, appConfigKey, &updateAC); err != nil {
|
||||
return 0
|
||||
}
|
||||
return updateAC.GetGeneration()
|
||||
}, 3*time.Second, time.Second).Should(Equal(int64(2)))
|
||||
|
||||
By("Check workload is re-created by reconciliation")
|
||||
recreatedCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
Expect(k8sClient.Get(ctx, cwObjKey, &recreatedCwObj)).Should(Succeed())
|
||||
By("Check workload was not created by reconciliation")
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
recreatedCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
return k8sClient.Get(ctx, cwObjKey, &recreatedCwObj)
|
||||
}, 5*time.Second, time.Second).Should(SatisfyAll(util.NotFoundMatcher{}))
|
||||
|
||||
By("Check trait is re-created by reconciliation")
|
||||
recreatedFooObj = unstructured.Unstructured{}
|
||||
|
||||
@@ -126,9 +126,8 @@ func TestApplyWorkloads(t *testing.T) {
|
||||
}
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
ws []v1alpha2.WorkloadStatus
|
||||
w []Workload
|
||||
ws []v1alpha2.WorkloadStatus
|
||||
w []Workload
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
@@ -330,7 +329,7 @@ func TestApplyWorkloads(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
mapper := mock.NewMockDiscoveryMapper()
|
||||
w := workloads{applicator: tc.applicator, rawClient: tc.rawClient, dm: mapper}
|
||||
err := w.Apply(tc.args.ctx, tc.args.ws, tc.args.w)
|
||||
err := w.Apply(context.TODO(), tc.args.ws, tc.args.w)
|
||||
|
||||
if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nw.Apply(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
|
||||
@@ -149,7 +149,8 @@ var _ = Describe("Test apply changes to trait", func() {
|
||||
APIVersion: "TraitDefinition",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bars.example.com",
|
||||
Name: "bars.example.com",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1alpha2.TraitDefinitionSpec{
|
||||
Reference: v1alpha2.DefinitionReference{
|
||||
@@ -236,31 +237,26 @@ spec:
|
||||
return appConfig.GetGeneration()
|
||||
}, time.Second, 300*time.Millisecond).Should(Equal(int64(2)))
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
Eventually(func() string {
|
||||
By("Reconcile & check updated trait")
|
||||
var traitObj unstructured.Unstructured
|
||||
Eventually(func() int64 {
|
||||
reconcileRetry(reconciler, req)
|
||||
if err := k8sClient.Get(ctx, appConfigKey, &appConfig); err != nil {
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
if appConfig.Status.Workloads == nil {
|
||||
reconcileRetry(reconciler, req)
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
return appConfig.Status.Workloads[0].Traits[0].Reference.Name
|
||||
}, 3*time.Second, time.Second).ShouldNot(BeEmpty())
|
||||
|
||||
By("Get changed trait object")
|
||||
traitName := appConfig.Status.Workloads[0].Traits[0].Reference.Name
|
||||
var traitObj unstructured.Unstructured
|
||||
traitObj.SetAPIVersion("example.com/v1")
|
||||
traitObj.SetKind("Bar")
|
||||
Eventually(func() int64 {
|
||||
traitName := appConfig.Status.Workloads[0].Traits[0].Reference.Name
|
||||
traitObj.SetAPIVersion("example.com/v1")
|
||||
traitObj.SetKind("Bar")
|
||||
if err := k8sClient.Get(ctx,
|
||||
client.ObjectKey{Namespace: namespace, Name: traitName}, &traitObj); err != nil {
|
||||
return 0
|
||||
}
|
||||
return traitObj.GetGeneration()
|
||||
}, 3*time.Second, time.Second).Should(Equal(int64(2)))
|
||||
}, 5*time.Second, time.Second).Should(Equal(int64(2)))
|
||||
|
||||
By("Check labels are removed")
|
||||
_, found, _ := unstructured.NestedString(traitObj.UnstructuredContent(), "metadata", "labels", "test.label")
|
||||
@@ -355,13 +351,19 @@ spec:
|
||||
}
|
||||
return appConfig.GetGeneration()
|
||||
}, time.Second, 300*time.Millisecond).Should(Equal(int64(2)))
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
changedTrait = unstructured.Unstructured{}
|
||||
changedTrait.SetAPIVersion("example.com/v1")
|
||||
changedTrait.SetKind("Bar")
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: traitName}, &changedTrait)).Should(Succeed())
|
||||
Eventually(func() int64 {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
changedTrait = unstructured.Unstructured{}
|
||||
changedTrait.SetAPIVersion("example.com/v1")
|
||||
changedTrait.SetKind("Bar")
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: traitName}, &changedTrait); err != nil {
|
||||
return 0
|
||||
}
|
||||
return changedTrait.GetGeneration()
|
||||
}, 5*time.Second, time.Second).Should(Equal(int64(3)))
|
||||
|
||||
By("Check AppConfig's change works")
|
||||
// changed a field
|
||||
v, _, _ = unstructured.NestedString(changedTrait.UnstructuredContent(), "spec", "valueChanged")
|
||||
|
||||
@@ -81,7 +81,7 @@ func (c *ComponentHandler) Generic(_ event.GenericEvent, _ workqueue.RateLimitin
|
||||
func isMatch(appConfigs *v1alpha2.ApplicationConfigurationList, compName string) (bool, types.NamespacedName) {
|
||||
for _, app := range appConfigs.Items {
|
||||
for _, comp := range app.Spec.Components {
|
||||
if comp.ComponentName == compName {
|
||||
if comp.ComponentName == compName || (strings.HasPrefix(comp.RevisionName, compName+"-")) {
|
||||
return true, types.NamespacedName{Namespace: app.Namespace, Name: app.Name}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,10 @@ func newTrue() *bool {
|
||||
func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtime.Object) ([]reconcile.Request, bool) {
|
||||
curComp := obj.(*v1alpha2.Component)
|
||||
comp := curComp.DeepCopy()
|
||||
// No generation changed, will not create revision
|
||||
if comp.Generation == comp.Status.ObservedGeneration {
|
||||
return nil, false
|
||||
}
|
||||
diff, curRevision := c.IsRevisionDiff(mt, comp)
|
||||
if !diff {
|
||||
// No difference, no need to create new revision.
|
||||
|
||||
@@ -18,50 +18,68 @@ package applicationconfiguration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
)
|
||||
|
||||
func TestCustomRevisionHook(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var req RevisionHookRequest
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(data, &req)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
if len(req.RelatedApps) != 1 {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("we should have only one relatedApps"))
|
||||
return
|
||||
}
|
||||
if req.Comp.Annotations == nil {
|
||||
req.Comp.Annotations = make(map[string]string)
|
||||
var RevisionHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var req RevisionHookRequest
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(data, &req)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
fmt.Println("got request from", req.Comp.Name)
|
||||
|
||||
if len(req.RelatedApps) != 1 {
|
||||
var abc []string
|
||||
for _, v := range req.RelatedApps {
|
||||
abc = append(abc, v.Name)
|
||||
}
|
||||
// we can add a check here for real world handler
|
||||
fmt.Printf("we should have only one relatedApps, but now %d: %s\n", len(req.RelatedApps), strings.Join(abc, ", "))
|
||||
}
|
||||
if req.Comp.Annotations == nil {
|
||||
req.Comp.Annotations = make(map[string]string)
|
||||
}
|
||||
if len(req.RelatedApps) > 0 {
|
||||
req.Comp.Annotations["app-name"] = req.RelatedApps[0].Name
|
||||
req.Comp.Annotations["app-namespace"] = req.RelatedApps[0].Namespace
|
||||
}
|
||||
a := &unstructured.Unstructured{}
|
||||
err = json.Unmarshal(req.Comp.Spec.Workload.Raw, a)
|
||||
fmt.Println("XX:", err)
|
||||
a.SetAnnotations(map[string]string{"time": time.Now().Format(time.RFC3339Nano)})
|
||||
data, _ = json.Marshal(a)
|
||||
req.Comp.Spec.Workload.Raw = data
|
||||
newdata, err := json.Marshal(req.Comp)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write(newdata)
|
||||
})
|
||||
|
||||
newdata, err := json.Marshal(req.Comp)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write(newdata)
|
||||
}))
|
||||
func TestCustomRevisionHook(t *testing.T) {
|
||||
srv := httptest.NewServer(RevisionHandler)
|
||||
defer srv.Close()
|
||||
compHandler := ComponentHandler{
|
||||
CustomRevisionHookURL: srv.URL,
|
||||
@@ -71,7 +89,4 @@ func TestCustomRevisionHook(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "app1", comp.Annotations["app-name"])
|
||||
assert.Equal(t, "default1", comp.Annotations["app-namespace"])
|
||||
|
||||
err = compHandler.customComponentRevisionHook([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "app1", Namespace: "default1"}}, {NamespacedName: types.NamespacedName{Name: "app2", Namespace: "default2"}}}, comp)
|
||||
assert.Equal(t, err.Error(), "httpcode(400) err: we should have only one relatedApps")
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestComponentHandler(t *testing.T) {
|
||||
// check component's status saved in corresponding controllerRevision
|
||||
assert.Equal(t, gotComp.Status.LatestRevision.Name, revisions.Items[0].Name)
|
||||
assert.Equal(t, gotComp.Status.LatestRevision.Revision, revisions.Items[0].Revision)
|
||||
// check component's status ObservedGeneration
|
||||
// check component's status AppliedGeneration
|
||||
assert.Equal(t, gotComp.Status.ObservedGeneration, comp.Generation)
|
||||
q.Done(item)
|
||||
// ============ Test Create Event End ===================
|
||||
|
||||
@@ -80,8 +80,23 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
AfterEach(func() {
|
||||
logf.Log.Info("Clean up resources")
|
||||
// delete the namespace with all its resources
|
||||
Expect(k8sClient.Delete(ctx, in)).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
|
||||
Expect(k8sClient.Delete(ctx, out)).Should(BeNil())
|
||||
ac := &v1alpha2.ApplicationConfiguration{}
|
||||
Expect(k8sClient.DeleteAllOf(ctx, ac, client.InNamespace(namespace))).Should(Succeed())
|
||||
cm := &corev1.ConfigMap{}
|
||||
Expect(k8sClient.DeleteAllOf(ctx, cm, client.InNamespace(namespace))).Should(Succeed())
|
||||
foo := &unstructured.Unstructured{}
|
||||
foo.SetAPIVersion("example.com/v1")
|
||||
foo.SetKind("Foo")
|
||||
Expect(k8sClient.DeleteAllOf(ctx, foo, client.InNamespace(namespace))).Should(Succeed())
|
||||
Eventually(func() bool {
|
||||
l := &unstructured.UnstructuredList{}
|
||||
l.SetAPIVersion("example.com/v1")
|
||||
l.SetKind("Foo")
|
||||
if err := k8sClient.List(ctx, l, client.InNamespace(namespace)); err != nil {
|
||||
return false
|
||||
}
|
||||
return len(l.Items) == 0
|
||||
}, 3*time.Second, time.Second).Should(BeTrue())
|
||||
})
|
||||
|
||||
// common function for verification
|
||||
@@ -114,11 +129,11 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, outFooKey, outFoo)
|
||||
if err != nil {
|
||||
// Try 3 (= 1s/300ms) times
|
||||
// Try 3 (= 3s/1s) times
|
||||
reconciler.Reconcile(req)
|
||||
}
|
||||
return err
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -159,7 +174,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
k8sClient.Get(ctx, outFooKey, outFoo)
|
||||
data, _, _ := unstructured.NestedString(outFoo.Object, "status", "key")
|
||||
return data
|
||||
}, time.Second, 300*time.Millisecond).Should(Equal("test"))
|
||||
}, 3*time.Second, time.Second).Should(Equal("test"))
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -175,7 +190,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
reconciler.Reconcile(req)
|
||||
k8sClient.Get(ctx, appconfigKey, appconfig)
|
||||
return appconfig.Status.Dependency.Unsatisfied
|
||||
}, 2*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
}
|
||||
|
||||
It("trait depends on another trait", func() {
|
||||
@@ -372,7 +387,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
Expect(k8sClient.Create(ctx, &appConfig)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appconfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -399,11 +414,11 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, outFooKey, outFoo)
|
||||
if err != nil {
|
||||
// Try 3 (= 1s/300ms) times
|
||||
// Try 3 (= 3s/1s) times
|
||||
reconciler.Reconcile(req)
|
||||
}
|
||||
return err
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
err := unstructured.SetNestedField(outFoo.Object, "test", "status", "key")
|
||||
Expect(err).Should(BeNil())
|
||||
@@ -423,11 +438,11 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
err = k8sClient.Get(ctx, appconfigKey, tempApp)
|
||||
tempApp.DeepCopyInto(newAppConfig)
|
||||
if err != nil || tempApp.Status.Dependency.Unsatisfied != nil {
|
||||
// Try 3 (= 1s/300ms) times
|
||||
// Try 3 (= 3s/1s) times
|
||||
reconciler.Reconcile(req)
|
||||
}
|
||||
return tempApp.Status.Dependency.Unsatisfied
|
||||
}(), time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}(), 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Checking that resource which accepts data is created now")
|
||||
logf.Log.Info("Checking on resource that inputs data", "Key", inFooKey)
|
||||
@@ -436,11 +451,11 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, outFooKey, outFoo)
|
||||
if err != nil {
|
||||
// Try 3 (= 1s/300ms) times
|
||||
// Try 3 (= 3s/1s) times
|
||||
reconciler.Reconcile(req)
|
||||
}
|
||||
return err
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
err = unstructured.SetNestedField(outFoo.Object, "test", "status", "key")
|
||||
Expect(err).Should(BeNil())
|
||||
err = unstructured.SetNestedField(outFoo.Object, "hash-v1", "status", "app-hash")
|
||||
@@ -453,7 +468,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
}
|
||||
s, _, _ := unstructured.NestedString(outFoo.Object, "status", "key")
|
||||
return s == "test"
|
||||
}, time.Second, 300*time.Millisecond).Should(BeTrue())
|
||||
}, 3*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
newAppConfig.Labels["app-hash"] = "hash-v2"
|
||||
By("Update newAppConfig & check successfully")
|
||||
@@ -464,10 +479,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
return false
|
||||
}
|
||||
return newAppConfig.Labels["app-hash"] == "hash-v2"
|
||||
}, time.Second, 300*time.Millisecond).Should(BeTrue())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
}, 3*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
By("Verify the appconfig's dependency should be unsatisfied, because requirementCondition valueFrom not match")
|
||||
depStatus := v1alpha2.DependencyStatus{
|
||||
@@ -492,9 +504,11 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
}}}},
|
||||
}
|
||||
Eventually(func() v1alpha2.DependencyStatus {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
k8sClient.Get(ctx, appconfigKey, newAppConfig)
|
||||
return newAppConfig.Status.Dependency
|
||||
}, time.Second, 300*time.Millisecond).Should(Equal(depStatus))
|
||||
}, 3*time.Second, time.Second).Should(Equal(depStatus))
|
||||
|
||||
By("Update trait resource to meet the requirement")
|
||||
Expect(k8sClient.Get(ctx, outFooKey, outFoo)).Should(BeNil()) // Get the latest before update
|
||||
@@ -508,7 +522,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
}
|
||||
s, _, _ := unstructured.NestedString(outFoo.Object, "status", "key")
|
||||
return s == "test-new"
|
||||
}, time.Second, 300*time.Millisecond).Should(BeTrue())
|
||||
}, 3*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -519,11 +533,11 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
tempAppConfig := &v1alpha2.ApplicationConfiguration{}
|
||||
err := k8sClient.Get(ctx, appconfigKey, tempAppConfig)
|
||||
if err != nil || tempAppConfig.Status.Dependency.Unsatisfied != nil {
|
||||
// Try 3 (= 1s/300ms) times
|
||||
// Try 3 (= 3s/1s) times
|
||||
reconciler.Reconcile(req)
|
||||
}
|
||||
return tempAppConfig.Status.Dependency.Unsatisfied
|
||||
}(), time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}(), 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Checking that resource which accepts data is updated")
|
||||
Expect(func() string {
|
||||
@@ -625,6 +639,9 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
Name: appConfigName,
|
||||
Namespace: namespace,
|
||||
}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appconfigKey, &v1alpha2.ApplicationConfiguration{})
|
||||
}, 3*time.Second, time.Second).Should(Succeed())
|
||||
By("Reconcile")
|
||||
req := reconcile.Request{NamespacedName: appconfigKey}
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -641,7 +658,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
reconciler.Reconcile(req)
|
||||
}
|
||||
return err
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Get reconciled AppConfig the first time")
|
||||
appconfig := &v1alpha2.ApplicationConfiguration{}
|
||||
@@ -669,7 +686,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
k8sClient.Get(ctx, outFooKey, outFoo)
|
||||
data, _, _ := unstructured.NestedSlice(outFoo.Object, "status", "complex2")
|
||||
return data
|
||||
}, time.Second, 300*time.Millisecond).Should(BeEquivalentTo(complex2))
|
||||
}, 3*time.Second, time.Second).Should(BeEquivalentTo(complex2))
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
@@ -680,7 +697,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() {
|
||||
reconciler.Reconcile(req)
|
||||
k8sClient.Get(ctx, appconfigKey, appconfig)
|
||||
return appconfig.Status.Dependency.Unsatisfied
|
||||
}, 2*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
}, 2*3*time.Second, time.Second).Should(BeNil())
|
||||
// Verification after satisfying dependency
|
||||
By("Checking that resource which accepts data is created now")
|
||||
inFooKey := client.ObjectKey{
|
||||
|
||||
@@ -39,7 +39,6 @@ import (
|
||||
|
||||
core "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/mock"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -99,8 +98,7 @@ func TestRenderComponents(t *testing.T) {
|
||||
trait ResourceRenderer
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
ac *v1alpha2.ApplicationConfiguration
|
||||
ac *v1alpha2.ApplicationConfiguration
|
||||
}
|
||||
type want struct {
|
||||
w []Workload
|
||||
@@ -525,7 +523,7 @@ func TestRenderComponents(t *testing.T) {
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := &components{tc.fields.client, mock.NewMockDiscoveryMapper(), tc.fields.params, tc.fields.workload, tc.fields.trait}
|
||||
got, _, err := r.Render(tc.args.ctx, tc.args.ac)
|
||||
got, _, err := r.Render(context.TODO(), tc.args.ac)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nr.Render(...): -want error, +got error:\n%s\n", tc.reason, diff)
|
||||
}
|
||||
@@ -813,8 +811,7 @@ func TestRenderTraitWithoutMetadataName(t *testing.T) {
|
||||
trait ResourceRenderer
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
ac *v1alpha2.ApplicationConfiguration
|
||||
ac *v1alpha2.ApplicationConfiguration
|
||||
}
|
||||
type want struct {
|
||||
w []Workload
|
||||
@@ -871,7 +868,7 @@ func TestRenderTraitWithoutMetadataName(t *testing.T) {
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := &components{tc.fields.client, mock.NewMockDiscoveryMapper(), tc.fields.params, tc.fields.workload, tc.fields.trait}
|
||||
got, _, _ := r.Render(tc.args.ctx, tc.args.ac)
|
||||
got, _, _ := r.Render(context.TODO(), tc.args.ac)
|
||||
if len(got) == 0 || len(got[0].Traits) == 0 || got[0].Traits[0].Object.GetName() != util.GenTraitName(componentName, ac.Spec.Components[0].Traits[0].DeepCopy(), "") {
|
||||
t.Errorf("\n%s\nr.Render(...): -want error, +got error:\n%s\n", tc.reason, "Trait name is NOT "+
|
||||
"automatically set.")
|
||||
|
||||
@@ -19,18 +19,21 @@ package applicationconfiguration
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
@@ -200,11 +203,10 @@ var _ = Describe("Test ApplicationConfiguration Component Revision Enabled trait
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check workload created successfully")
|
||||
Eventually(func() error {
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: compName + "-v1"}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
@@ -295,11 +297,11 @@ var _ = Describe("Test ApplicationConfiguration Component Revision Enabled trait
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Reconcile for new revision")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check new revision workload created successfully")
|
||||
Eventually(func() error {
|
||||
By("Reconcile for new revision")
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: compName + "-v2"}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
@@ -341,3 +343,824 @@ var _ = Describe("Test ApplicationConfiguration Component Revision Enabled trait
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test Component Revision Enabled with custom component revision hook", func() {
|
||||
const (
|
||||
namespace = "revision-enable-test2"
|
||||
compName = "revision-test-comp2"
|
||||
)
|
||||
var (
|
||||
ctx = context.Background()
|
||||
component v1alpha2.Component
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {})
|
||||
|
||||
AfterEach(func() {
|
||||
// delete the namespace with all its resources
|
||||
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).
|
||||
Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
|
||||
})
|
||||
|
||||
It("custom component change revision lead to revision difference, it should not loop infinitely create", func() {
|
||||
srv := httptest.NewServer(RevisionHandler)
|
||||
defer srv.Close()
|
||||
customComponentHandler := &ComponentHandler{Client: k8sClient, RevisionLimit: 100, Logger: logging.NewLogrLogger(ctrl.Log.WithName("component-handler")), CustomRevisionHookURL: srv.URL}
|
||||
getDeploy := func(image string) *v1.Deployment {
|
||||
return &v1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||
"app": compName,
|
||||
}},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
|
||||
"app": compName,
|
||||
}},
|
||||
Spec: corev1.PodSpec{Containers: []corev1.Container{{
|
||||
Name: "wordpress",
|
||||
Image: image,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "wordpress",
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
}
|
||||
}
|
||||
component = v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
Kind: "Component",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:4.6.1-apache"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create namespace")
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Create(ctx, &ns)
|
||||
},
|
||||
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
By("Create Component")
|
||||
Expect(k8sClient.Create(ctx, &component)).Should(Succeed())
|
||||
|
||||
By("component handler will automatically create controller revision")
|
||||
Expect(func() bool {
|
||||
_, ok := customComponentHandler.createControllerRevision(component.DeepCopy(), component.DeepCopy())
|
||||
return ok
|
||||
}()).Should(BeTrue())
|
||||
|
||||
By("it should not create again for the same generation component")
|
||||
cmpV1 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV1)).Should(Succeed())
|
||||
Expect(func() bool {
|
||||
_, ok := customComponentHandler.createControllerRevision(cmpV1, cmpV1)
|
||||
return ok
|
||||
}()).Should(BeFalse())
|
||||
|
||||
var crList v1.ControllerRevisionList
|
||||
By("Check controller revision created successfully")
|
||||
Eventually(func() error {
|
||||
labels := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
ControllerRevisionComponentLabel: compName,
|
||||
},
|
||||
}
|
||||
selector, err := metav1.LabelSelectorAsSelector(labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sClient.List(ctx, &crList, &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(crList.Items) != 1 {
|
||||
return fmt.Errorf("want only 1 revision created but got %d", len(crList.Items))
|
||||
}
|
||||
return nil
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("===================================== Start to Update =========================================")
|
||||
cmpV2 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV2)).Should(Succeed())
|
||||
cmpV2.Spec.Workload = runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:v2"),
|
||||
}
|
||||
By("Update Component")
|
||||
Expect(k8sClient.Update(ctx, cmpV2)).Should(Succeed())
|
||||
By("component handler will automatically create a ne controller revision")
|
||||
Expect(func() bool { _, ok := componentHandler.createControllerRevision(cmpV2, cmpV2); return ok }()).Should(BeTrue())
|
||||
|
||||
cmpV3 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV3)).Should(Succeed())
|
||||
Expect(func() bool { _, ok := componentHandler.createControllerRevision(cmpV3, cmpV3); return ok }()).Should(BeFalse())
|
||||
|
||||
By("Check controller revision created successfully")
|
||||
Eventually(func() error {
|
||||
labels := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
ControllerRevisionComponentLabel: compName,
|
||||
},
|
||||
}
|
||||
selector, err := metav1.LabelSelectorAsSelector(labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sClient.List(ctx, &crList, &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(crList.Items) != 2 {
|
||||
return fmt.Errorf("there should be exactly 2 revision created but got %d", len(crList.Items))
|
||||
}
|
||||
return nil
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Component Revision Enabled with apply once only force", func() {
|
||||
const (
|
||||
namespace = "revision-and-apply-once-force"
|
||||
appName = "revision-apply-once"
|
||||
compName = "revision-apply-once-comp"
|
||||
)
|
||||
var (
|
||||
ctx = context.Background()
|
||||
wr v1.Deployment
|
||||
component v1alpha2.Component
|
||||
appConfig v1alpha2.ApplicationConfiguration
|
||||
appConfigKey = client.ObjectKey{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
}
|
||||
req = reconcile.Request{NamespacedName: appConfigKey}
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {})
|
||||
|
||||
AfterEach(func() {
|
||||
// delete the namespace with all its resources
|
||||
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).
|
||||
Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
|
||||
})
|
||||
|
||||
It("revision enabled should create workload with revisionName and work upgrade with new revision successfully", func() {
|
||||
|
||||
getDeploy := func(image string) *v1.Deployment {
|
||||
return &v1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||
"app": compName,
|
||||
}},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
|
||||
"app": compName,
|
||||
}},
|
||||
Spec: corev1.PodSpec{Containers: []corev1.Container{{
|
||||
Name: "wordpress",
|
||||
Image: image,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "wordpress",
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
}
|
||||
}
|
||||
component = v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
Kind: "Component",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:4.6.1-apache"),
|
||||
},
|
||||
},
|
||||
}
|
||||
appConfig = v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
By("Create namespace")
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Create(ctx, &ns)
|
||||
},
|
||||
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
By("Create Component")
|
||||
Expect(k8sClient.Create(ctx, &component)).Should(Succeed())
|
||||
cmpV1 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV1)).Should(Succeed())
|
||||
|
||||
By("component handler will automatically create controller revision")
|
||||
Expect(func() bool {
|
||||
_, ok := componentHandler.createControllerRevision(cmpV1, cmpV1)
|
||||
return ok
|
||||
}()).Should(BeTrue())
|
||||
var crList v1.ControllerRevisionList
|
||||
By("Check controller revision created successfully")
|
||||
Eventually(func() error {
|
||||
labels := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
ControllerRevisionComponentLabel: compName,
|
||||
},
|
||||
}
|
||||
selector, err := metav1.LabelSelectorAsSelector(labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sClient.List(ctx, &crList, &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(crList.Items) != 1 {
|
||||
return fmt.Errorf("want only 1 revision created but got %d", len(crList.Items))
|
||||
}
|
||||
return nil
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Create an ApplicationConfiguration")
|
||||
appConfig = v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
RevisionName: compName + "-v1",
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "rollout-revision",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"key": "test1",
|
||||
},
|
||||
}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
By("Creat appConfig & check successfully")
|
||||
Expect(k8sClient.Create(ctx, &appConfig)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
reconciler.applyOnceOnlyMode = "force"
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check workload created successfully")
|
||||
var workloadKey1 = client.ObjectKey{Namespace: namespace, Name: compName + "-v1"}
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, workloadKey1, &wr)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Check workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
|
||||
By("Delete the workload")
|
||||
Expect(k8sClient.Delete(ctx, &wr)).Should(BeNil())
|
||||
|
||||
By("Check reconcile again and no error will happen")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check workload will not created after reconcile because apply once force enabled")
|
||||
Expect(k8sClient.Get(ctx, workloadKey1, &wr)).Should(SatisfyAll(util.NotFoundMatcher{}))
|
||||
Expect(k8sClient.Get(ctx, appConfigKey, &appConfig)).Should(BeNil())
|
||||
By("update the trait of ac")
|
||||
appConfig.Spec.Components[0].Traits[0].Trait = runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "rollout-revision",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"key": "test2",
|
||||
},
|
||||
}}}
|
||||
Expect(k8sClient.Update(ctx, &appConfig)).Should(Succeed())
|
||||
|
||||
By("Reconcile and Check appconfig condition should not have error")
|
||||
reconcileRetry(reconciler, req)
|
||||
Eventually(func() string {
|
||||
By("Reconcile again and should not have error")
|
||||
reconcileRetry(reconciler, req)
|
||||
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(appConfig.Status.Conditions) != 1 {
|
||||
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
|
||||
}
|
||||
return string(appConfig.Status.Conditions[0].Reason)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess"))
|
||||
time.Sleep(time.Second)
|
||||
By("Check workload will not created even AC changed because apply once force working")
|
||||
Expect(k8sClient.Get(ctx, workloadKey1, &wr)).Should(SatisfyAll(util.NotFoundMatcher{}))
|
||||
By("Check the trait was updated as expected")
|
||||
var tr unstructured.Unstructured
|
||||
Eventually(func() error {
|
||||
tr.SetAPIVersion("example.com/v1")
|
||||
tr.SetKind("Foo")
|
||||
var traitKey = client.ObjectKey{Namespace: namespace, Name: appConfig.Status.Workloads[0].Traits[0].Reference.Name}
|
||||
return k8sClient.Get(ctx, traitKey, &tr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
Expect(tr.Object["spec"]).Should(BeEquivalentTo(map[string]interface{}{"key": "test2"}))
|
||||
|
||||
By("===================================== Start to Upgrade revision of component =========================================")
|
||||
cmpV2 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV2)).Should(Succeed())
|
||||
cmpV2.Spec.Workload = runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:v2"),
|
||||
}
|
||||
By("Update Component")
|
||||
Expect(k8sClient.Update(ctx, cmpV2)).Should(Succeed())
|
||||
By("component handler will automatically create a ne controller revision")
|
||||
Expect(func() bool { _, ok := componentHandler.createControllerRevision(cmpV2, cmpV2); return ok }()).Should(BeTrue())
|
||||
By("Check controller revision created successfully")
|
||||
Eventually(func() error {
|
||||
labels := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
ControllerRevisionComponentLabel: compName,
|
||||
},
|
||||
}
|
||||
selector, err := metav1.LabelSelectorAsSelector(labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sClient.List(ctx, &crList, &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(crList.Items) != 2 {
|
||||
return fmt.Errorf("there should be exactly 2 revision created but got %d", len(crList.Items))
|
||||
}
|
||||
return nil
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Update appConfig & check successfully")
|
||||
appConfig.Spec.Components[0].RevisionName = compName + "-v2"
|
||||
appConfig.Spec.Components[0].Traits[0].Trait = runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "rollout-revision",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"key": "test3",
|
||||
},
|
||||
}}}
|
||||
Expect(k8sClient.Update(ctx, &appConfig)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Reconcile for new revision")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check new revision workload created successfully")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: compName + "-v2"}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Check the new workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
Expect(wr.Spec.Template.Spec.Containers[0].Image).Should(BeEquivalentTo("wordpress:v2"))
|
||||
|
||||
By("Check the new workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
|
||||
By("Check reconcile again and no error will happen")
|
||||
reconcileRetry(reconciler, req)
|
||||
By("Check appconfig condition should not have error")
|
||||
Eventually(func() string {
|
||||
By("Once more Reconcile and should not have error")
|
||||
reconcileRetry(reconciler, req)
|
||||
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(appConfig.Status.Conditions) != 1 {
|
||||
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
|
||||
}
|
||||
return string(appConfig.Status.Conditions[0].Reason)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess"))
|
||||
By("Check trait was updated as expected")
|
||||
Eventually(func() error {
|
||||
tr.SetAPIVersion("example.com/v1")
|
||||
tr.SetKind("Foo")
|
||||
var traitKey = client.ObjectKey{Namespace: namespace, Name: appConfig.Status.Workloads[0].Traits[0].Reference.Name}
|
||||
return k8sClient.Get(ctx, traitKey, &tr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
Expect(tr.Object["spec"]).Should(BeEquivalentTo(map[string]interface{}{"key": "test3"}))
|
||||
reconciler.applyOnceOnlyMode = "off"
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Component Revision Enabled with workloadName set and apply once only force", func() {
|
||||
const (
|
||||
namespace = "revision-and-workload-name-specified"
|
||||
appName = "revision-apply-once2"
|
||||
compName = "revision-apply-once-comp2"
|
||||
specifiedNameBase = "specified-name-base"
|
||||
specifiedNameV1 = "specified-name-v1"
|
||||
specifiedNameV2 = "specified-name-v2"
|
||||
)
|
||||
var (
|
||||
ctx = context.Background()
|
||||
wr v1.Deployment
|
||||
component v1alpha2.Component
|
||||
appConfig v1alpha2.ApplicationConfiguration
|
||||
appConfigKey = client.ObjectKey{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
}
|
||||
req = reconcile.Request{NamespacedName: appConfigKey}
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {})
|
||||
|
||||
AfterEach(func() {
|
||||
// delete the namespace with all its resources
|
||||
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).
|
||||
Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
|
||||
})
|
||||
|
||||
It("revision enabled should create workload with specified name protect delete with replicas larger than 0", func() {
|
||||
|
||||
getDeploy := func(image, name string) *v1.Deployment {
|
||||
return &v1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
},
|
||||
Spec: v1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||
"app": compName,
|
||||
}},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
|
||||
"app": compName,
|
||||
}},
|
||||
Spec: corev1.PodSpec{Containers: []corev1.Container{{
|
||||
Name: "wordpress",
|
||||
Image: image,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: "wordpress",
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
}
|
||||
}
|
||||
component = v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
Kind: "Component",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:4.6.1-apache", specifiedNameBase),
|
||||
},
|
||||
},
|
||||
}
|
||||
appConfig = v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
By("Create namespace")
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Create(ctx, &ns)
|
||||
},
|
||||
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
By("Create Component")
|
||||
Expect(k8sClient.Create(ctx, &component)).Should(Succeed())
|
||||
cmpV1 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV1)).Should(Succeed())
|
||||
|
||||
By("component handler will automatically create controller revision")
|
||||
Expect(func() bool {
|
||||
_, ok := componentHandler.createControllerRevision(cmpV1, cmpV1)
|
||||
return ok
|
||||
}()).Should(BeTrue())
|
||||
var crList v1.ControllerRevisionList
|
||||
By("Check controller revision created successfully")
|
||||
Eventually(func() error {
|
||||
labels := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
ControllerRevisionComponentLabel: compName,
|
||||
},
|
||||
}
|
||||
selector, err := metav1.LabelSelectorAsSelector(labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sClient.List(ctx, &crList, &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(crList.Items) != 1 {
|
||||
return fmt.Errorf("want only 1 revision created but got %d", len(crList.Items))
|
||||
}
|
||||
return nil
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Create an ApplicationConfiguration")
|
||||
appConfig = v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
RevisionName: compName + "-v1",
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
By("Creat appConfig & check successfully")
|
||||
Expect(k8sClient.Create(ctx, &appConfig)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Reconcile")
|
||||
reconciler.applyOnceOnlyMode = "force"
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check workload created successfully")
|
||||
var workloadKey1 = client.ObjectKey{Namespace: namespace, Name: specifiedNameBase}
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
return k8sClient.Get(ctx, workloadKey1, &wr)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Check workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
|
||||
By("Check reconcile again and no error will happen")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
Expect(k8sClient.Get(ctx, appConfigKey, &appConfig)).Should(BeNil())
|
||||
|
||||
By("===================================== Start to Upgrade revision of component =========================================")
|
||||
cmpV2 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV2)).Should(Succeed())
|
||||
cmpV2.Spec.Workload = runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:v2", specifiedNameV1),
|
||||
}
|
||||
By("Update Component")
|
||||
Expect(k8sClient.Update(ctx, cmpV2)).Should(Succeed())
|
||||
By("component handler will automatically create a ne controller revision")
|
||||
Expect(func() bool { _, ok := componentHandler.createControllerRevision(cmpV2, cmpV2); return ok }()).Should(BeTrue())
|
||||
By("Check controller revision created successfully")
|
||||
Eventually(func() error {
|
||||
labels := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
ControllerRevisionComponentLabel: compName,
|
||||
},
|
||||
}
|
||||
selector, err := metav1.LabelSelectorAsSelector(labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sClient.List(ctx, &crList, &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(crList.Items) != 2 {
|
||||
return fmt.Errorf("there should be exactly 2 revision created but got %d", len(crList.Items))
|
||||
}
|
||||
return nil
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Update appConfig & check successfully")
|
||||
appConfig.Spec.Components[0].RevisionName = compName + "-v2"
|
||||
|
||||
Expect(k8sClient.Update(ctx, &appConfig)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Reconcile for new revision")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check new revision workload created successfully")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: specifiedNameV1}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Check the new workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
Expect(wr.Spec.Template.Spec.Containers[0].Image).Should(BeEquivalentTo("wordpress:v2"))
|
||||
|
||||
By("Check the new workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
|
||||
By("Check reconcile again")
|
||||
reconcileRetry(reconciler, req)
|
||||
|
||||
By("Check appconfig condition should have error")
|
||||
Eventually(func() string {
|
||||
reconcileRetry(reconciler, req)
|
||||
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(appConfig.Status.Conditions) != 1 {
|
||||
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
|
||||
}
|
||||
By(fmt.Sprintf("Reconcile with condition %v", appConfig.Status.Conditions[0]))
|
||||
return string(appConfig.Status.Conditions[0].Reason)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileError"))
|
||||
|
||||
By("Check the old workload still there")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: specifiedNameBase}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Check the old workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
Expect(wr.Spec.Template.Spec.Containers[0].Image).Should(BeEquivalentTo("wordpress:4.6.1-apache"))
|
||||
|
||||
wr.Spec.Replicas = pointer.Int32Ptr(0)
|
||||
Expect(k8sClient.Update(ctx, &wr)).Should(Succeed())
|
||||
|
||||
By("Reconcile Again and appconfig condition should not have error")
|
||||
Eventually(func() string {
|
||||
By("Once more Reconcile and should not have error")
|
||||
reconcileRetry(reconciler, req)
|
||||
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(appConfig.Status.Conditions) != 1 {
|
||||
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
|
||||
}
|
||||
return string(appConfig.Status.Conditions[0].Reason)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess"))
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: specifiedNameBase}, &wr)).Should(SatisfyAny(util.NotFoundMatcher{}))
|
||||
|
||||
By("===================================== Start to Upgrade revision of component again =========================================")
|
||||
cmpV3 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: compName}, cmpV3)).Should(Succeed())
|
||||
cmpV3.Spec.Workload = runtime.RawExtension{
|
||||
Object: getDeploy("wordpress:v3", specifiedNameV2),
|
||||
}
|
||||
By("Update Component")
|
||||
Expect(k8sClient.Update(ctx, cmpV3)).Should(Succeed())
|
||||
By("component handler will automatically create a ne controller revision")
|
||||
Expect(func() bool { _, ok := componentHandler.createControllerRevision(cmpV3, cmpV3); return ok }()).Should(BeTrue())
|
||||
By("Update the AC and add the revisionEnabled Trait")
|
||||
appConfig.Spec.Components[0].RevisionName = compName + "-v3"
|
||||
appConfig.Spec.Components[0].Traits = []v1alpha2.ComponentTrait{
|
||||
{Trait: runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "example.com/v1",
|
||||
"kind": "Foo",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "rollout-revision",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"key": "test3",
|
||||
},
|
||||
}}}},
|
||||
}
|
||||
Expect(k8sClient.Update(ctx, &appConfig)).Should(Succeed())
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
Expect(len(appConfig.Spec.Components[0].Traits)).Should(BeEquivalentTo(1))
|
||||
|
||||
By("Check reconcile again and no error will happen, revisionEnabled will skip delete")
|
||||
reconcileRetry(reconciler, req)
|
||||
By("Check appconfig condition should not have error")
|
||||
Eventually(func() string {
|
||||
By("Once more Reconcile and should not have error")
|
||||
reconcileRetry(reconciler, req)
|
||||
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(appConfig.Status.Conditions) != 1 {
|
||||
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
|
||||
}
|
||||
return string(appConfig.Status.Conditions[0].Reason)
|
||||
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess"))
|
||||
|
||||
By("Check new revision workload created successfully")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: specifiedNameV2}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
By("Check the new workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
Expect(wr.Spec.Template.Spec.Containers[0].Image).Should(BeEquivalentTo("wordpress:v3"))
|
||||
By("Check the new workload should only have 1 generation")
|
||||
Expect(wr.GetGeneration()).Should(BeEquivalentTo(1))
|
||||
By("Check the old workload still there")
|
||||
Eventually(func() error {
|
||||
reconcileRetry(reconciler, req)
|
||||
var workloadKey = client.ObjectKey{Namespace: namespace, Name: specifiedNameV1}
|
||||
return k8sClient.Get(ctx, workloadKey, &wr)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
reconciler.applyOnceOnlyMode = "off"
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package applicationconfiguration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -44,7 +46,7 @@ var k8sClient client.Client
|
||||
var scheme = runtime.NewScheme()
|
||||
var crd crdv1.CustomResourceDefinition
|
||||
|
||||
func TestReconcilder(t *testing.T) {
|
||||
func TestReconcilerSuit(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
@@ -55,9 +57,16 @@ func TestReconcilder(t *testing.T) {
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
ctx := context.Background()
|
||||
By("Bootstrapping test environment")
|
||||
var yamlPath string
|
||||
if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set {
|
||||
yamlPath = "../../../../../test/compatibility-test/testdata"
|
||||
} else {
|
||||
yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds")
|
||||
}
|
||||
logf.Log.Info("start applicationconfiguration suit test", "yaml_path", yamlPath)
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("../../../../..", "charts/vela-core/crds"), // this has all the required CRDs,
|
||||
yamlPath, // this has all the required CRDs,
|
||||
},
|
||||
}
|
||||
var err error
|
||||
@@ -150,13 +159,14 @@ var _ = BeforeSuite(func(done Done) {
|
||||
}, time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
Expect(mapping.Resource.Resource).Should(Equal("foo"))
|
||||
|
||||
reconciler = NewReconciler(mgr, dm, WithLogger(logging.NewLogrLogger(ctrl.Log.WithName("suit-test-appconfig"))))
|
||||
reconciler = NewReconciler(mgr, dm, logging.NewLogrLogger(ctrl.Log.WithName("suit-test-appconfig")))
|
||||
componentHandler = &ComponentHandler{Client: k8sClient, RevisionLimit: 100, Logger: logging.NewLogrLogger(ctrl.Log.WithName("component-handler"))}
|
||||
|
||||
By("Creating workload definition and trait definition")
|
||||
wd := v1alpha2.WorkloadDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo.example.com",
|
||||
Name: "foo.example.com",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1alpha2.WorkloadDefinitionSpec{
|
||||
Reference: v1alpha2.DefinitionReference{
|
||||
@@ -166,7 +176,8 @@ var _ = BeforeSuite(func(done Done) {
|
||||
}
|
||||
td := v1alpha2.TraitDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo.example.com",
|
||||
Name: "foo.example.com",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1alpha2.TraitDefinitionSpec{
|
||||
Reference: v1alpha2.DefinitionReference{
|
||||
@@ -177,7 +188,8 @@ var _ = BeforeSuite(func(done Done) {
|
||||
|
||||
rollout := v1alpha2.TraitDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rollout-revision",
|
||||
Name: "rollout-revision",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1alpha2.TraitDefinitionSpec{
|
||||
Reference: v1alpha2.DefinitionReference{
|
||||
@@ -186,6 +198,8 @@ var _ = BeforeSuite(func(done Done) {
|
||||
RevisionEnabled: true,
|
||||
},
|
||||
}
|
||||
definitonNs := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}}
|
||||
Expect(k8sClient.Create(context.Background(), definitonNs.DeepCopy())).Should(BeNil())
|
||||
|
||||
// For some reason, WorkloadDefinition is created as a Cluster scope object
|
||||
Expect(k8sClient.Create(ctx, &wd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
@@ -31,7 +31,8 @@ func (r *Reconciler) extractWorkloadTypeAndGVK(ctx context.Context, componentLis
|
||||
}
|
||||
// get the workload definition
|
||||
// the validator webhook has checked that source and the target are the same type
|
||||
wd, err := oamutil.GetWorkloadDefinition(ctx, r, componentType)
|
||||
wd := new(corev1alpha2.WorkloadDefinition)
|
||||
err := oamutil.GetDefinition(ctx, r, wd, componentType)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, fmt.Sprintf("failed to get workload definition %s", componentType))
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const appDeployFinalizer = "finalizers.applicationdeployment.oam.dev"
|
||||
@@ -71,6 +72,8 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
|
||||
// TODO: check if the target/source has changed
|
||||
r.handleFinalizer(&appDeploy)
|
||||
|
||||
ctx = oamutil.SetNnamespaceInCtx(ctx, appDeploy.Namespace)
|
||||
|
||||
// Get the target application
|
||||
var targetApp corev1alpha2.Application
|
||||
var sourceApp *corev1alpha2.Application
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package applicationdeployment
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -37,9 +38,16 @@ var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
var yamlPath string
|
||||
if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set {
|
||||
yamlPath = "../../../../../test/compatibility-test/testdata"
|
||||
} else {
|
||||
yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds")
|
||||
}
|
||||
logf.Log.Info("start application deployment suit test", "yaml_path", yamlPath)
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("../../../..", "charts/vela-core/crds"), // this has all the required CRDs,
|
||||
yamlPath, // this has all the required CRDs,
|
||||
filepath.Join("..", "config", "crd", "bases")},
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,9 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
if err := r.Get(ctx, req.NamespacedName, &manualScalar); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
ctx = util.SetNnamespaceInCtx(ctx, manualScalar.Namespace)
|
||||
|
||||
r.log.Info("Get the manualscalar trait", "ReplicaCount", manualScalar.Spec.ReplicaCount,
|
||||
"Annotations", manualScalar.GetAnnotations())
|
||||
// find the resource object to record the event to, default is the parent appConfig.
|
||||
|
||||
@@ -20,10 +20,7 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/autoscaler"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/routes"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
)
|
||||
|
||||
@@ -33,23 +30,14 @@ func Setup(mgr ctrl.Manager, disableCaps string) error {
|
||||
switch disableCaps {
|
||||
case common.DisableNoneCaps:
|
||||
functions = []func(ctrl.Manager) error{
|
||||
metrics.Setup, podspecworkload.Setup, routes.Setup, autoscaler.Setup,
|
||||
podspecworkload.Setup,
|
||||
}
|
||||
case common.DisableAllCaps:
|
||||
default:
|
||||
disableCapsSet := utils.StoreInSet(disableCaps)
|
||||
if !disableCapsSet.Contains(common.MetricsControllerName) {
|
||||
functions = append(functions, metrics.Setup)
|
||||
}
|
||||
if !disableCapsSet.Contains(common.PodspecWorkloadControllerName) {
|
||||
functions = append(functions, podspecworkload.Setup)
|
||||
}
|
||||
if !disableCapsSet.Contains(common.RouteControllerName) {
|
||||
functions = append(functions, routes.Setup)
|
||||
}
|
||||
if !disableCapsSet.Contains(common.AutoscaleControllerName) {
|
||||
functions = append(functions, autoscaler.Setup)
|
||||
}
|
||||
}
|
||||
|
||||
for _, setup := range functions {
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 autoscaler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
// nolint:golint
|
||||
const (
|
||||
SpecWarningTargetWorkloadNotSet = "Spec.targetWorkload is not set"
|
||||
SpecWarningStartAtTimeFormat = "startAt is not in the right format, which should be like `12:01`"
|
||||
SpecWarningStartAtTimeRequired = "spec.triggers.condition.startAt: Required value"
|
||||
SpecWarningDurationTimeRequired = "spec.triggers.condition.duration: Required value"
|
||||
SpecWarningReplicasRequired = "spec.triggers.condition.replicas: Required value"
|
||||
SpecWarningDurationTimeNotInRightFormat = "spec.triggers.condition.duration: not in the right format"
|
||||
)
|
||||
|
||||
// ReconcileWaitResult is the time to wait between reconciliation.
|
||||
var ReconcileWaitResult = reconcile.Result{RequeueAfter: 30 * time.Second}
|
||||
|
||||
// Reconciler reconciles a Autoscaler object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
}
|
||||
|
||||
// Reconcile is the main logic for autoscaler controller
|
||||
// +kubebuilder:rbac:groups=standard.oam.dev,resources=autoscalers,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=standard.oam.dev,resources=autoscalers/status,verbs=get;update;patch
|
||||
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.Log.WithValues("autoscaler", req.NamespacedName)
|
||||
log.Info("Reconciling Autoscaler...")
|
||||
ctx := context.Background()
|
||||
var scaler v1alpha1.Autoscaler
|
||||
if err := r.Get(ctx, req.NamespacedName, &scaler); err != nil {
|
||||
log.Error(err, "Failed to get trait", "traitName", scaler.Name)
|
||||
return ReconcileWaitResult, client.IgnoreNotFound(err)
|
||||
}
|
||||
log.Info("Retrieved trait Autoscaler", "APIVersion", scaler.APIVersion, "Kind", scaler.Kind)
|
||||
|
||||
// find the resource object to record the event to, default is the parent appConfig.
|
||||
eventObj, err := util.LocateParentAppConfig(ctx, r.Client, &scaler)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to find the parent resource", "Autoscaler", scaler.Name)
|
||||
return util.ReconcileWaitResult, util.PatchCondition(ctx, r, &scaler,
|
||||
cpv1alpha1.ReconcileError(fmt.Errorf(util.ErrLocateAppConfig)))
|
||||
}
|
||||
if eventObj == nil {
|
||||
// fallback to workload itself
|
||||
log.Info("There is no parent resource", "Autoscaler", scaler.Name)
|
||||
eventObj = &scaler
|
||||
}
|
||||
|
||||
// Fetch the instance to which the trait refers to
|
||||
workload, err := util.FetchWorkload(ctx, r, log, &scaler)
|
||||
if err != nil {
|
||||
log.Error(err, "Error while fetching the workload", "workload reference",
|
||||
scaler.GetWorkloadReference())
|
||||
r.record.Event(&scaler, event.Warning(common.ErrLocatingWorkload, err))
|
||||
return util.ReconcileWaitResult,
|
||||
util.PatchCondition(ctx, r, &scaler,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingWorkload)))
|
||||
}
|
||||
|
||||
// Fetch the child resources list from the corresponding workload
|
||||
resources, err := util.FetchWorkloadChildResources(ctx, log, r, r.dm, workload)
|
||||
if err != nil {
|
||||
log.Error(err, "Error while fetching the workload child resources", "workload", workload.UnstructuredContent())
|
||||
r.record.Event(eventObj, event.Warning(util.ErrFetchChildResources, err))
|
||||
return util.ReconcileWaitResult, util.PatchCondition(ctx, r, &scaler,
|
||||
cpv1alpha1.ReconcileError(fmt.Errorf(util.ErrFetchChildResources)))
|
||||
}
|
||||
resources = append(resources, workload)
|
||||
|
||||
targetWorkloadSetFlag := false
|
||||
for _, res := range resources {
|
||||
// Keda only support these four built-in workload now.
|
||||
if res.GetKind() == "Deployment" || res.GetKind() == "StatefulSet" || res.GetKind() == "DaemonSet" || res.GetKind() == "ReplicaSet" {
|
||||
scaler.Spec.TargetWorkload = v1alpha1.TargetWorkload{
|
||||
APIVersion: res.GetAPIVersion(),
|
||||
Kind: res.GetKind(),
|
||||
Name: res.GetName(),
|
||||
}
|
||||
targetWorkloadSetFlag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if no child resource found, set the workload as target workload
|
||||
if !targetWorkloadSetFlag {
|
||||
scaler.Spec.TargetWorkload = v1alpha1.TargetWorkload{
|
||||
APIVersion: workload.GetAPIVersion(),
|
||||
Kind: workload.GetKind(),
|
||||
Name: workload.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
namespace := req.NamespacedName.Namespace
|
||||
if err := r.scaleByKEDA(scaler, namespace, log); err != nil {
|
||||
return ReconcileWaitResult, err
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// SetupWithManager will setup with event recorder
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("Autoscaler")).
|
||||
WithAnnotations("controller", "Autoscaler")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&v1alpha1.Autoscaler{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// Setup adds a controller that reconciles MetricsTrait.
|
||||
func Setup(mgr ctrl.Manager) error {
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("Autoscaler"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
package autoscaler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
kedav1alpha1 "github.com/wonderflow/keda-api/api/v1alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
func (r *Reconciler) scaleByKEDA(scaler v1alpha1.Autoscaler, namespace string, log logr.Logger) error {
|
||||
ctx := context.Background()
|
||||
minReplicas := scaler.Spec.MinReplicas
|
||||
maxReplicas := scaler.Spec.MaxReplicas
|
||||
triggers := scaler.Spec.Triggers
|
||||
scalerName := scaler.Name
|
||||
targetWorkload := scaler.Spec.TargetWorkload
|
||||
|
||||
var kedaTriggers []kedav1alpha1.ScaleTriggers
|
||||
var err error
|
||||
for _, t := range triggers {
|
||||
if t.Type == CronType {
|
||||
cronKedaTriggers, reason, err := r.prepareKEDACronScalerTriggerSpec(scaler, t)
|
||||
if err != nil {
|
||||
log.Error(err, reason)
|
||||
r.record.Event(&scaler, event.Warning(event.Reason(reason), err))
|
||||
return err
|
||||
}
|
||||
kedaTriggers = append(kedaTriggers, cronKedaTriggers...)
|
||||
} else {
|
||||
kedaTriggers = append(kedaTriggers, kedav1alpha1.ScaleTriggers{
|
||||
Type: string(t.Type),
|
||||
Name: t.Name,
|
||||
Metadata: t.Condition,
|
||||
|
||||
// TODO(wonderflow): add auth in the future
|
||||
AuthenticationRef: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
spec := kedav1alpha1.ScaledObjectSpec{
|
||||
ScaleTargetRef: &kedav1alpha1.ScaleTarget{
|
||||
APIVersion: targetWorkload.APIVersion,
|
||||
Kind: targetWorkload.Kind,
|
||||
Name: targetWorkload.Name,
|
||||
},
|
||||
MinReplicaCount: minReplicas,
|
||||
MaxReplicaCount: maxReplicas,
|
||||
Triggers: kedaTriggers,
|
||||
}
|
||||
var scaleObj kedav1alpha1.ScaledObject
|
||||
err = r.Client.Get(ctx, types.NamespacedName{Name: scalerName, Namespace: namespace}, &scaleObj)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
scaleObj := kedav1alpha1.ScaledObject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: scalerName,
|
||||
Namespace: namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: scaler.APIVersion,
|
||||
Kind: scaler.Kind,
|
||||
UID: scaler.GetUID(),
|
||||
Name: scalerName,
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
if err := r.Client.Create(ctx, &scaleObj); err != nil {
|
||||
log.Error(err, "failed to create KEDA ScaledObj", "ScaledObject", scaleObj)
|
||||
return err
|
||||
}
|
||||
log.Info("KEDA ScaledObj created", "ScaledObjectName", scalerName)
|
||||
}
|
||||
} else {
|
||||
scaleObj.Spec = spec
|
||||
if err := r.Client.Update(ctx, &scaleObj); err != nil {
|
||||
log.Error(err, "failed to update KEDA ScaledObj", "ScaledObject", scaleObj)
|
||||
return err
|
||||
}
|
||||
log.Info("KEDA ScaledObj updated", "ScaledObjectName", scalerName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CronTypeCondition defines the cron type for autoscaler
|
||||
type CronTypeCondition struct {
|
||||
// StartAt is the time when the scaler starts, in format `"HHMM"` for example, "08:00"
|
||||
StartAt string `json:"startAt,omitempty"`
|
||||
|
||||
// Duration means how long the target scaling will keep, after the time of duration, the scaling will stop
|
||||
Duration string `json:"duration,omitempty"`
|
||||
|
||||
// Days means in which days the condition will take effect
|
||||
Days string `json:"days,omitempty"`
|
||||
|
||||
// Replicas is the expected replicas
|
||||
Replicas string `json:"replicas,omitempty"`
|
||||
|
||||
// Timezone defines the time zone, default to the timezone of the Kubernetes cluster
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
// GetCronTypeCondition will get condition from map
|
||||
func GetCronTypeCondition(condition map[string]string) (*CronTypeCondition, error) {
|
||||
data, err := json.Marshal(condition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cronCon CronTypeCondition
|
||||
if err = json.Unmarshal(data, &cronCon); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cronCon, nil
|
||||
}
|
||||
|
||||
// prepareKEDACronScalerTriggerSpec converts Autoscaler spec into KEDA Cron scaler spec
|
||||
func (r *Reconciler) prepareKEDACronScalerTriggerSpec(scaler v1alpha1.Autoscaler, t v1alpha1.Trigger) ([]kedav1alpha1.ScaleTriggers, string, error) {
|
||||
var kedaTriggers []kedav1alpha1.ScaleTriggers
|
||||
targetWorkload := scaler.Spec.TargetWorkload
|
||||
if targetWorkload.Name == "" {
|
||||
err := errors.New(SpecWarningTargetWorkloadNotSet)
|
||||
return kedaTriggers, SpecWarningTargetWorkloadNotSet, err
|
||||
}
|
||||
triggerCondition, err := GetCronTypeCondition(t.Condition)
|
||||
if err != nil {
|
||||
return nil, "convert cron condition failed", err
|
||||
}
|
||||
startAt := triggerCondition.StartAt
|
||||
if startAt == "" {
|
||||
return kedaTriggers, SpecWarningStartAtTimeRequired, errors.New(SpecWarningStartAtTimeRequired)
|
||||
}
|
||||
duration := triggerCondition.Duration
|
||||
if duration == "" {
|
||||
return kedaTriggers, SpecWarningDurationTimeRequired, errors.New(SpecWarningDurationTimeRequired)
|
||||
}
|
||||
startTime, err := time.Parse("15:04", startAt)
|
||||
if err != nil {
|
||||
return kedaTriggers, SpecWarningStartAtTimeFormat, err
|
||||
}
|
||||
var startHour, startMinute int
|
||||
startHour = startTime.Hour()
|
||||
startMinute = startTime.Minute()
|
||||
|
||||
durationTime, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
return kedaTriggers, SpecWarningDurationTimeNotInRightFormat, err
|
||||
}
|
||||
durationHour := durationTime.Hours()
|
||||
durationMin := int(durationTime.Minutes()) % 60
|
||||
endMinite := startMinute + durationMin
|
||||
endHour := int(durationHour) + startHour
|
||||
|
||||
if endMinite >= 60 {
|
||||
endMinite %= 60
|
||||
endHour++
|
||||
}
|
||||
var durationOneMoreDay int
|
||||
if endHour >= 24 {
|
||||
endHour %= 24
|
||||
durationOneMoreDay = 1
|
||||
}
|
||||
replicas, err := strconv.Atoi(triggerCondition.Replicas)
|
||||
if err != nil {
|
||||
return nil, "parse replica failed", err
|
||||
}
|
||||
if replicas == 0 {
|
||||
return kedaTriggers, SpecWarningReplicasRequired, errors.New(SpecWarningReplicasRequired)
|
||||
}
|
||||
|
||||
timezone := triggerCondition.Timezone
|
||||
|
||||
days := strings.Split(triggerCondition.Days, ",")
|
||||
var dayNo []int
|
||||
|
||||
for i, d := range days {
|
||||
d = strings.TrimSpace(d)
|
||||
days[i] = d
|
||||
var found = false
|
||||
for i := 0; i < 7; i++ {
|
||||
if strings.EqualFold(time.Weekday(i).String(), d) {
|
||||
dayNo = append(dayNo, i)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, "", fmt.Errorf("wrong format %s, should be one of %v", d,
|
||||
[]string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"})
|
||||
}
|
||||
}
|
||||
|
||||
for idx, n := range dayNo {
|
||||
kedaTrigger := kedav1alpha1.ScaleTriggers{
|
||||
Type: string(t.Type),
|
||||
Name: t.Name + "-" + days[idx],
|
||||
Metadata: map[string]string{
|
||||
"timezone": timezone,
|
||||
"start": fmt.Sprintf("%d %d * * %d", startMinute, startHour, n),
|
||||
"end": fmt.Sprintf("%d %d * * %d", endMinite, endHour, (n+durationOneMoreDay)%7),
|
||||
"desiredReplicas": strconv.Itoa(replicas),
|
||||
},
|
||||
}
|
||||
kedaTriggers = append(kedaTriggers, kedaTrigger)
|
||||
}
|
||||
return kedaTriggers, "", nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 autoscaler
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = standardv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
package autoscaler
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// constants used in autoscaler controller
|
||||
const (
|
||||
CronType v1alpha1.TriggerType = "cron"
|
||||
CPUType v1alpha1.TriggerType = "cpu"
|
||||
)
|
||||
@@ -1,366 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
errApplyServiceMonitor = "failed to apply the service monitor"
|
||||
errFailDiscoveryLabels = "failed to discover labels from pod template, use workload labels directly"
|
||||
servicePort = 4848
|
||||
)
|
||||
|
||||
var (
|
||||
serviceMonitorKind = reflect.TypeOf(monitoring.ServiceMonitor{}).Name()
|
||||
serviceMonitorAPIVersion = monitoring.SchemeGroupVersion.String()
|
||||
)
|
||||
|
||||
var (
|
||||
// ServiceMonitorNSName is the name of the namespace in which the serviceMonitor resides
|
||||
// it must be the same that the prometheus operator is listening to
|
||||
ServiceMonitorNSName = "monitoring"
|
||||
)
|
||||
|
||||
// GetOAMServiceLabel will return oamServiceLabel as the pre-defined labels for any serviceMonitor
|
||||
// created by the MetricsTrait, prometheus operator listens on this
|
||||
func GetOAMServiceLabel() map[string]string {
|
||||
return map[string]string{
|
||||
"k8s-app": "oam",
|
||||
"controller": "metricsTrait",
|
||||
}
|
||||
}
|
||||
|
||||
// Reconciler reconciles a MetricsTrait object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
}
|
||||
|
||||
// Reconcile is the main logic for metric trait controller
|
||||
// +kubebuilder:rbac:groups=standard.oam.dev,resources=metricstraits,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=standard.oam.dev,resources=metricstraits/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=*,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=*/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=core.oam.dev,resources=*,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core.oam.dev,resources=*/status,verbs=get;
|
||||
// +kubebuilder:rbac:groups="",resources=events,verbs=get;list;create;update;patch
|
||||
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
ctx := context.Background()
|
||||
mLog := r.Log.WithValues("metricstrait", req.NamespacedName)
|
||||
mLog.Info("Reconcile metricstrait trait")
|
||||
// fetch the trait
|
||||
var metricsTrait v1alpha1.MetricsTrait
|
||||
if err := r.Get(ctx, req.NamespacedName, &metricsTrait); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
mLog.Info("Get the metricsTrait trait",
|
||||
"metrics end point", metricsTrait.Spec.ScrapeService,
|
||||
"workload reference", metricsTrait.Spec.WorkloadReference,
|
||||
"labels", metricsTrait.GetLabels())
|
||||
|
||||
// find the resource object to record the event to, default is the parent appConfig.
|
||||
eventObj, err := oamutil.LocateParentAppConfig(ctx, r.Client, &metricsTrait)
|
||||
if eventObj == nil {
|
||||
// fallback to workload itself
|
||||
mLog.Error(err, "add events to metricsTrait itself", "name", metricsTrait.Name)
|
||||
eventObj = &metricsTrait
|
||||
}
|
||||
if metricsTrait.Spec.ScrapeService.Enabled != nil && !*metricsTrait.Spec.ScrapeService.Enabled {
|
||||
r.record.Event(eventObj, event.Normal("Metrics Trait disabled", "no op"))
|
||||
r.gcOrphanServiceMonitor(ctx, mLog, &metricsTrait)
|
||||
return ctrl.Result{}, oamutil.PatchCondition(ctx, r, &metricsTrait, cpv1alpha1.ReconcileSuccess())
|
||||
}
|
||||
|
||||
// Fetch the workload instance to which we want to expose metrics
|
||||
workload, err := oamutil.FetchWorkload(ctx, r, mLog, &metricsTrait)
|
||||
if err != nil {
|
||||
mLog.Error(err, "Error while fetching the workload", "workload reference",
|
||||
metricsTrait.GetWorkloadReference())
|
||||
r.record.Event(eventObj, event.Warning(common.ErrLocatingWorkload, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingWorkload)))
|
||||
}
|
||||
var targetPort = metricsTrait.Spec.ScrapeService.TargetPort
|
||||
// try to see if the workload already has services as child resources
|
||||
serviceLabel, err := r.fetchServicesLabel(ctx, mLog, workload, targetPort)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
r.record.Event(eventObj, event.Warning(common.ErrLocatingService, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingService)))
|
||||
} else if serviceLabel == nil {
|
||||
// TODO: use podMonitor instead?
|
||||
// no service with the targetPort found, we will create a service that talks to the targetPort
|
||||
serviceLabel, targetPort, err = r.createService(ctx, mLog, workload, &metricsTrait)
|
||||
if err != nil {
|
||||
r.record.Event(eventObj, event.Warning(common.ErrCreatingService, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrCreatingService)))
|
||||
}
|
||||
}
|
||||
|
||||
metricsTrait.Status.Port = targetPort
|
||||
metricsTrait.Status.SelectorLabels = serviceLabel
|
||||
|
||||
// construct the serviceMonitor that hooks the service to the prometheus server
|
||||
serviceMonitor := constructServiceMonitor(&metricsTrait, targetPort)
|
||||
// server side apply the serviceMonitor, only the fields we set are touched
|
||||
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner(metricsTrait.GetUID())}
|
||||
if err := r.Patch(ctx, serviceMonitor, client.Apply, applyOpts...); err != nil {
|
||||
mLog.Error(err, "Failed to apply to serviceMonitor")
|
||||
r.record.Event(eventObj, event.Warning(errApplyServiceMonitor, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, errApplyServiceMonitor)))
|
||||
}
|
||||
r.record.Event(eventObj, event.Normal("ServiceMonitor created",
|
||||
fmt.Sprintf("successfully server side patched a serviceMonitor `%s`", serviceMonitor.Name)))
|
||||
|
||||
r.gcOrphanServiceMonitor(ctx, mLog, &metricsTrait)
|
||||
(&metricsTrait).SetConditions(cpv1alpha1.ReconcileSuccess())
|
||||
return ctrl.Result{}, errors.Wrap(r.UpdateStatus(ctx, &metricsTrait), common.ErrUpdateStatus)
|
||||
}
|
||||
|
||||
// fetch the label of the service that is associated with the workload
|
||||
func (r *Reconciler) fetchServicesLabel(ctx context.Context, mLog logr.Logger,
|
||||
workload *unstructured.Unstructured, targetPort intstr.IntOrString) (map[string]string, error) {
|
||||
// Fetch the child resources list from the corresponding workload
|
||||
resources, err := oamutil.FetchWorkloadChildResources(ctx, mLog, r, r.dm, workload)
|
||||
if err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
mLog.Error(err, "Error while fetching the workload child resources", "workload kind", workload.GetKind(),
|
||||
"workload name", workload.GetName())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// find the service that has the port
|
||||
for _, childRes := range resources {
|
||||
if childRes.GetAPIVersion() == common.ServiceAPIVersion && childRes.GetKind() == common.ServiceKind {
|
||||
ports, _, _ := unstructured.NestedSlice(childRes.Object, "spec", "ports")
|
||||
for _, port := range ports {
|
||||
servicePort, _ := port.(corev1.ServicePort)
|
||||
if servicePort.TargetPort == targetPort {
|
||||
return childRes.GetLabels(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// create a service that targets the exposed workload pod
|
||||
func (r *Reconciler) createService(ctx context.Context, mLog logr.Logger, workload *unstructured.Unstructured,
|
||||
metricsTrait *v1alpha1.MetricsTrait) (map[string]string, intstr.IntOrString, error) {
|
||||
oamService := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: common.ServiceKind,
|
||||
APIVersion: common.ServiceAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "oam-" + workload.GetName(),
|
||||
Namespace: workload.GetNamespace(),
|
||||
Labels: GetOAMServiceLabel(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: metricsTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: metricsTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: metricsTrait.GetUID(),
|
||||
Name: metricsTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
},
|
||||
}
|
||||
var targetPort = metricsTrait.Spec.ScrapeService.TargetPort
|
||||
// assign selector
|
||||
ports, labels, err := utils.DiscoveryFromPodTemplate(workload, "spec", "template")
|
||||
if err != nil {
|
||||
mLog.Info(errFailDiscoveryLabels, "err", err)
|
||||
if len(metricsTrait.Spec.ScrapeService.TargetSelector) == 0 {
|
||||
// we assumed that the pods have the same label as the workload if no discoverable
|
||||
oamService.Spec.Selector = workload.GetLabels()
|
||||
} else {
|
||||
oamService.Spec.Selector = metricsTrait.Spec.ScrapeService.TargetSelector
|
||||
}
|
||||
} else {
|
||||
oamService.Spec.Selector = labels
|
||||
}
|
||||
if targetPort.String() == "0" {
|
||||
if len(ports) == 0 {
|
||||
return nil, intstr.IntOrString{}, fmt.Errorf("no ports discovered or specified")
|
||||
}
|
||||
// choose the first one if no port specified
|
||||
targetPort = ports[0]
|
||||
}
|
||||
oamService.Spec.Ports = []corev1.ServicePort{
|
||||
{
|
||||
Port: servicePort,
|
||||
TargetPort: targetPort,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
}
|
||||
// server side apply the service, only the fields we set are touched
|
||||
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner(metricsTrait.GetUID())}
|
||||
if err := r.Patch(ctx, oamService, client.Apply, applyOpts...); err != nil {
|
||||
mLog.Error(err, "Failed to apply to service")
|
||||
return nil, intstr.IntOrString{}, err
|
||||
}
|
||||
return oamService.Spec.Selector, targetPort, nil
|
||||
}
|
||||
|
||||
// remove all service monitors that are no longer used
|
||||
func (r *Reconciler) gcOrphanServiceMonitor(ctx context.Context, mLog logr.Logger,
|
||||
metricsTrait *v1alpha1.MetricsTrait) {
|
||||
var gcCandidate = metricsTrait.Status.ServiceMonitorName
|
||||
if metricsTrait.Spec.ScrapeService.Enabled != nil && !*metricsTrait.Spec.ScrapeService.Enabled {
|
||||
// initialize it to be an empty list, gc everything
|
||||
metricsTrait.Status.ServiceMonitorName = ""
|
||||
} else {
|
||||
// re-initialize to the current service monitor
|
||||
metricsTrait.Status.ServiceMonitorName = metricsTrait.Name
|
||||
}
|
||||
if gcCandidate == metricsTrait.Name {
|
||||
return
|
||||
}
|
||||
if err := r.Delete(ctx, &monitoring.ServiceMonitor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: serviceMonitorKind,
|
||||
APIVersion: serviceMonitorAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gcCandidate,
|
||||
Namespace: metricsTrait.GetNamespace(),
|
||||
},
|
||||
}, client.GracePeriodSeconds(10)); err != nil {
|
||||
mLog.Error(err, "Failed to delete serviceMonitor", "name", gcCandidate, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// construct a serviceMonitor given a metrics trait along with a label selector pointing to the underlying service
|
||||
func constructServiceMonitor(metricsTrait *v1alpha1.MetricsTrait, targetPort intstr.IntOrString) *monitoring.ServiceMonitor {
|
||||
return &monitoring.ServiceMonitor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: serviceMonitorKind,
|
||||
APIVersion: serviceMonitorAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: metricsTrait.Name,
|
||||
Namespace: ServiceMonitorNSName,
|
||||
Labels: GetOAMServiceLabel(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: metricsTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: metricsTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: metricsTrait.GetUID(),
|
||||
Name: metricsTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: monitoring.ServiceMonitorSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchLabels: GetOAMServiceLabel(),
|
||||
},
|
||||
// we assumed that the service is in the same namespace as the trait
|
||||
NamespaceSelector: monitoring.NamespaceSelector{
|
||||
MatchNames: []string{metricsTrait.Namespace},
|
||||
},
|
||||
Endpoints: []monitoring.Endpoint{
|
||||
{
|
||||
TargetPort: &targetPort,
|
||||
Path: metricsTrait.Spec.ScrapeService.Path,
|
||||
Scheme: metricsTrait.Spec.ScrapeService.Scheme,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetupWithManager setup Reconciler with ctrl.Manager
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("MetricsTrait")).
|
||||
WithAnnotations("controller", "metricsTrait")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&v1alpha1.MetricsTrait{}).
|
||||
Owns(&monitoring.ServiceMonitor{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// UpdateStatus updates v1alpha1.MetricsTrait's Status with retry.RetryOnConflict
|
||||
func (r *Reconciler) UpdateStatus(ctx context.Context, mt *v1alpha1.MetricsTrait, opts ...client.UpdateOption) error {
|
||||
status := mt.DeepCopy().Status
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
if err = r.Get(ctx, types.NamespacedName{Namespace: mt.Namespace, Name: mt.Name}, mt); err != nil {
|
||||
return
|
||||
}
|
||||
mt.Status = status
|
||||
return r.Status().Update(ctx, mt, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
// Setup adds a controller that reconciles MetricsTrait.
|
||||
func Setup(mgr ctrl.Manager) error {
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reconciler := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("MetricsTrait"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
}
|
||||
return reconciler.SetupWithManager(mgr)
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
metricsTraitKind = reflect.TypeOf(v1alpha1.MetricsTrait{}).Name()
|
||||
metricsTraitAPIVersion = v1alpha1.SchemeGroupVersion.String()
|
||||
deploymentKind = reflect.TypeOf(appsv1.Deployment{}).Name()
|
||||
deploymentAPIVersion = appsv1.SchemeGroupVersion.String()
|
||||
)
|
||||
|
||||
var _ = Describe("Metrics Trait Integration Test", func() {
|
||||
// common var init
|
||||
ctx := context.Background()
|
||||
namespaceName := "metricstrait-integration-test"
|
||||
traitLabel := map[string]string{"trait": "metricsTraitBase"}
|
||||
deployLabel := map[string]string{"standard.oam.dev": "oam-test-deployment"}
|
||||
podPort := 8080
|
||||
targetPort := intstr.FromInt(podPort)
|
||||
metricsPath := "/notMetrics"
|
||||
scheme := "http"
|
||||
var ns corev1.Namespace
|
||||
var metricsTraitBase v1alpha1.MetricsTrait
|
||||
var workloadBase appsv1.Deployment
|
||||
|
||||
BeforeEach(func() {
|
||||
logf.Log.Info("[TEST] Set up resources before an integration test")
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespaceName,
|
||||
},
|
||||
}
|
||||
By("Create the Namespace for test")
|
||||
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
|
||||
metricsTraitBase = v1alpha1.MetricsTrait{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: metricsTraitKind,
|
||||
APIVersion: metricsTraitAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns.Name,
|
||||
Labels: traitLabel,
|
||||
},
|
||||
Spec: v1alpha1.MetricsTraitSpec{
|
||||
ScrapeService: v1alpha1.ScapeServiceEndPoint{
|
||||
TargetPort: targetPort,
|
||||
Path: metricsPath,
|
||||
Scheme: scheme,
|
||||
Enabled: pointer.BoolPtr(true),
|
||||
},
|
||||
WorkloadReference: runtimev1alpha1.TypedReference{
|
||||
APIVersion: deploymentAPIVersion,
|
||||
Kind: deploymentKind,
|
||||
},
|
||||
},
|
||||
}
|
||||
workloadBase = appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns.Name,
|
||||
Labels: deployLabel,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: deployLabel,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: deployLabel,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container-name",
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
Command: []string{"containerCommand"},
|
||||
Args: []string{"containerArguments"},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(podPort),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Control-runtime test environment has a bug that can't delete resources like deployment/namespaces
|
||||
// We have to use different names to segregate between tests
|
||||
logf.Log.Info("[TEST] Clean up resources after an integration test")
|
||||
})
|
||||
|
||||
It("Test with deployment as workloadBase without selector", func() {
|
||||
testName := "deploy-without-selector"
|
||||
By("Create the deployment as the workloadBase")
|
||||
workload := workloadBase
|
||||
workload.Name = testName + "-workload"
|
||||
Expect(k8sClient.Create(ctx, &workload)).ToNot(HaveOccurred())
|
||||
|
||||
By("Create the metrics trait pointing to the workloadBase")
|
||||
metricsTrait := metricsTraitBase
|
||||
metricsTrait.Name = testName + "-trait"
|
||||
metricsTrait.Spec.WorkloadReference.Name = workload.Name
|
||||
Expect(k8sClient.Create(ctx, &metricsTrait)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdService := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: "oam-" + workload.GetName()},
|
||||
&createdService)
|
||||
},
|
||||
time.Second*10, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdService.Spec.Ports)
|
||||
Expect(createdService.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(createdService.Labels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(len(createdService.Spec.Ports)).Should(Equal(1))
|
||||
Expect(createdService.Spec.Ports[0].Port).Should(BeEquivalentTo(servicePort))
|
||||
Expect(createdService.Spec.Selector).Should(Equal(deployLabel))
|
||||
By("Check that we have created the serviceMonitor in the pre-defined namespaceName")
|
||||
var serviceMonitor monitoringv1.ServiceMonitor
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ServiceMonitorNSName, Name: metricsTrait.GetName()},
|
||||
&serviceMonitor)
|
||||
},
|
||||
time.Second*5, time.Millisecond*50).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created serviceMonitor", "service end ports", serviceMonitor.Spec.Endpoints)
|
||||
Expect(serviceMonitor.GetNamespace()).Should(Equal(ServiceMonitorNSName))
|
||||
Expect(serviceMonitor.Spec.Selector.MatchLabels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(serviceMonitor.Spec.Selector.MatchExpressions).Should(BeNil())
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.MatchNames).Should(Equal([]string{metricsTrait.Namespace}))
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.Any).Should(BeFalse())
|
||||
Expect(len(serviceMonitor.Spec.Endpoints)).Should(Equal(1))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Port).Should(BeEmpty())
|
||||
Expect(*serviceMonitor.Spec.Endpoints[0].TargetPort).Should(BeEquivalentTo(targetPort))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Scheme).Should(Equal(scheme))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Path).Should(Equal(metricsPath))
|
||||
})
|
||||
|
||||
It("Test with deployment as workloadBase selector", func() {
|
||||
testName := "deploy-with-selector"
|
||||
By("Create the deployment as the workloadBase")
|
||||
workload := workloadBase.DeepCopy()
|
||||
workload.Name = testName + "-workload"
|
||||
Expect(k8sClient.Create(ctx, workload)).ToNot(HaveOccurred())
|
||||
|
||||
By("Create the metrics trait pointing to the workloadBase")
|
||||
podSelector := map[string]string{"podlabel": "goodboy"}
|
||||
metricsTrait := metricsTraitBase
|
||||
metricsTrait.Name = testName + "-trait"
|
||||
metricsTrait.Spec.WorkloadReference.Name = workload.Name
|
||||
metricsTrait.Spec.ScrapeService.TargetSelector = podSelector
|
||||
Expect(k8sClient.Create(ctx, &metricsTrait)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdService := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: "oam-" + workload.GetName()},
|
||||
&createdService)
|
||||
},
|
||||
time.Second*10, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdService.Spec.Ports)
|
||||
Expect(createdService.Labels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(createdService.Spec.Selector).Should(Equal(deployLabel))
|
||||
By("Check that we have created the serviceMonitor in the pre-defined namespaceName")
|
||||
var serviceMonitor monitoringv1.ServiceMonitor
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ServiceMonitorNSName, Name: metricsTrait.GetName()},
|
||||
&serviceMonitor)
|
||||
},
|
||||
time.Second*5, time.Millisecond*50).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created serviceMonitor", "service end ports", serviceMonitor.Spec.Endpoints)
|
||||
Expect(serviceMonitor.Spec.Selector.MatchLabels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(serviceMonitor.Spec.Selector.MatchExpressions).Should(BeNil())
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.MatchNames).Should(Equal([]string{metricsTrait.Namespace}))
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.Any).Should(BeFalse())
|
||||
Expect(len(serviceMonitor.Spec.Endpoints)).Should(Equal(1))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Port).Should(BeEmpty())
|
||||
Expect(*serviceMonitor.Spec.Endpoints[0].TargetPort).Should(BeEquivalentTo(targetPort))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Scheme).Should(Equal(scheme))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Path).Should(Equal(metricsPath))
|
||||
})
|
||||
})
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
|
||||
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var controllerDone chan struct{}
|
||||
var serviceMonitorNS corev1.Namespace
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
serviceMonitorNS = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ServiceMonitorNSName,
|
||||
},
|
||||
}
|
||||
By("Bootstrapping test environment")
|
||||
useExistCluster := false
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("../../../../..", "charts/vela-core/crds"), // this has all the required CRDs,
|
||||
},
|
||||
UseExistingCluster: &useExistCluster,
|
||||
}
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = standardv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = monitoringv1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = oamCore.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
By("Create the k8s client")
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
By("Starting the metrics trait controller in the background")
|
||||
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
MetricsBindAddress: "0",
|
||||
Port: 9443,
|
||||
LeaderElection: false,
|
||||
LeaderElectionID: "9f6dad5a.oam.dev",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("MetricsTrait"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
controllerDone = make(chan struct{}, 1)
|
||||
// +kubebuilder:scaffold:builder
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
Expect(mgr.Start(controllerDone)).ToNot(HaveOccurred())
|
||||
}()
|
||||
|
||||
By("Create the serviceMonitor namespace")
|
||||
Expect(k8sClient.Create(context.Background(), &serviceMonitorNS)).ToNot(HaveOccurred())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("Stop the metricTrait controller")
|
||||
close(controllerDone)
|
||||
By("Delete the serviceMonitor namespace")
|
||||
Expect(k8sClient.Delete(context.Background(), &serviceMonitorNS,
|
||||
client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
|
||||
By("Tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// TypeNginx is a type of route implementation using [Nginx-Ingress](https://github.com/kubernetes/ingress-nginx)
|
||||
const TypeNginx = "nginx"
|
||||
|
||||
// TypeContour is a type of route implementation using [contour ingress](https://github.com/projectcontour/contour)
|
||||
const TypeContour = "contour"
|
||||
|
||||
const (
|
||||
// StatusReady represents status is ready
|
||||
StatusReady = "Ready"
|
||||
// StatusSynced represents status is synced, this mean the controller has reconciled but not ready
|
||||
StatusSynced = "Synced"
|
||||
)
|
||||
|
||||
// RouteIngress is an interface of route ingress implementation
|
||||
type RouteIngress interface {
|
||||
Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress
|
||||
CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runtimev1alpha1.Condition)
|
||||
}
|
||||
|
||||
// GetRouteIngress will get real implementation from type, we could support more in the future.
|
||||
func GetRouteIngress(provider string, client client.Client) (RouteIngress, error) {
|
||||
var routeIngress RouteIngress
|
||||
switch provider {
|
||||
case TypeNginx, "":
|
||||
routeIngress = &Nginx{Client: client}
|
||||
case TypeContour:
|
||||
routeIngress = &Contour{Client: client}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknow route ingress provider '%v', only '%s' is supported now", provider, TypeNginx)
|
||||
}
|
||||
return routeIngress, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetRouteIngress(t *testing.T) {
|
||||
_, err := GetRouteIngress("nginx", nil)
|
||||
assert.NoError(t, err)
|
||||
_, err = GetRouteIngress("", nil)
|
||||
assert.NoError(t, err)
|
||||
_, err = GetRouteIngress("istio", nil)
|
||||
assert.EqualError(t, err, "unknow route ingress provider 'istio', only 'nginx' is supported now")
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
cmmeta "github.com/wonderflow/cert-manager-api/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Contour is Contour ingress implementation
|
||||
type Contour struct {
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
var _ RouteIngress = &Contour{}
|
||||
|
||||
// CheckStatus will check status of the ingress
|
||||
func (n *Contour) CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runtimev1alpha1.Condition) {
|
||||
ctx := context.Background()
|
||||
// check issuer
|
||||
if routeTrait.Spec.TLS != nil && routeTrait.Spec.TLS.Type != standardv1alpha1.ClusterIssuer {
|
||||
tls := routeTrait.Spec.TLS
|
||||
var issuer certmanager.Issuer
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: tls.IssuerName}, &issuer)
|
||||
if err != nil || len(issuer.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("issuer '%v' is pending to be resolved by controller", tls.IssuerName)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
condition := issuer.Status.Conditions[0]
|
||||
if condition.Status != cmmeta.ConditionTrue {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(condition.Reason),
|
||||
Message: condition.Message}}
|
||||
}
|
||||
}
|
||||
// check ingress
|
||||
ingresses := n.Construct(routeTrait)
|
||||
for _, in := range ingresses {
|
||||
|
||||
// Check Certificate
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var cert certmanager.Certificate
|
||||
// check cert
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: in.Name + "-cert"}, &cert)
|
||||
if err != nil || len(cert.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("CertificateRequest %s is pending to be resolved by controller", in.Name)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
certcondition := cert.Status.Conditions[0]
|
||||
if certcondition.Status != cmmeta.ConditionTrue || certcondition.Type != certmanager.CertificateConditionReady {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(certcondition.Reason),
|
||||
Message: certcondition.Message}}
|
||||
}
|
||||
}
|
||||
|
||||
// Check Ingress
|
||||
var ingress v1beta1.Ingress
|
||||
if err := n.Client.Get(ctx, types.NamespacedName{Namespace: in.Namespace, Name: in.Name}, &ingress); err != nil {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: err.Error()}}
|
||||
}
|
||||
ingressvalue := ingress.Status.LoadBalancer.Ingress
|
||||
if len(ingressvalue) < 1 || (ingressvalue[0].IP == "" && ingressvalue[0].Hostname == "") {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonCreating,
|
||||
Message: fmt.Sprintf("IP/Hostname of %s ingress is generating", in.Name)}}
|
||||
}
|
||||
}
|
||||
return StatusReady, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeReady, Status: v1.ConditionTrue,
|
||||
Reason: runtimev1alpha1.ReasonAvailable, LastTransitionTime: metav1.Now()}}
|
||||
}
|
||||
|
||||
// Construct will construct ingress from route
|
||||
func (*Contour) Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress {
|
||||
|
||||
// Don't create ingress if no host set, this is used for local K8s cluster demo and the route trait will create K8s service only.
|
||||
if routeTrait.Spec.Host == "" || strings.Contains(routeTrait.Spec.Host, "localhost") || strings.Contains(routeTrait.Spec.Host, "127.0.0.1") {
|
||||
return nil
|
||||
}
|
||||
var ingresses []*v1beta1.Ingress
|
||||
for idx, rule := range routeTrait.Spec.Rules {
|
||||
name := rule.Name
|
||||
if name == "" {
|
||||
name = strconv.Itoa(idx)
|
||||
}
|
||||
backend := rule.Backend
|
||||
if backend == nil || backend.BackendService == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var annotations = make(map[string]string)
|
||||
|
||||
annotations["kubernetes.io/ingress.class"] = TypeContour
|
||||
|
||||
// SSL
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var issuerAnn = "cert-manager.io/issuer"
|
||||
if routeTrait.Spec.TLS.Type == standardv1alpha1.ClusterIssuer {
|
||||
issuerAnn = "cert-manager.io/cluster-issuer"
|
||||
}
|
||||
annotations[issuerAnn] = routeTrait.Spec.TLS.IssuerName
|
||||
}
|
||||
// todo Rewrite
|
||||
|
||||
// todo Custom headers
|
||||
|
||||
// todo Send timeout
|
||||
|
||||
// Read timeout
|
||||
if backend.ReadTimeout != 0 {
|
||||
annotations["projectcontour.io/response-timeout"] = strconv.Itoa(backend.ReadTimeout)
|
||||
}
|
||||
|
||||
ingress := &v1beta1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: routeTrait.Name + "-" + name,
|
||||
Namespace: routeTrait.Namespace,
|
||||
Annotations: annotations,
|
||||
Labels: routeTrait.GetLabels(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: routeTrait.GetUID(),
|
||||
Name: routeTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
ingress.Spec.TLS = []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{routeTrait.Spec.Host},
|
||||
SecretName: routeTrait.Name + "-" + name + "-cert",
|
||||
},
|
||||
}
|
||||
}
|
||||
if rule.DefaultBackend != nil {
|
||||
ingress.Spec.Backend = &v1beta1.IngressBackend{
|
||||
Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: &rule.DefaultBackend.APIVersion,
|
||||
Kind: rule.DefaultBackend.Kind,
|
||||
Name: rule.DefaultBackend.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
ingress.Spec.Rules = []v1beta1.IngressRule{
|
||||
{
|
||||
Host: routeTrait.Spec.Host,
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{
|
||||
Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: rule.Path,
|
||||
Backend: v1beta1.IngressBackend{
|
||||
ServiceName: backend.BackendService.ServiceName,
|
||||
ServicePort: backend.BackendService.Port,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
ingresses = append(ingresses, ingress)
|
||||
}
|
||||
return ingresses
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
func TestContourConstruct(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
routeTrait *standardv1alpha1.Route
|
||||
exp []*v1beta1.Ingress
|
||||
}{
|
||||
"normal case": {
|
||||
routeTrait: &standardv1alpha1.Route{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Route",
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test",
|
||||
},
|
||||
Spec: standardv1alpha1.RouteSpec{
|
||||
Host: "test.abc",
|
||||
TLS: &standardv1alpha1.TLS{
|
||||
IssuerName: "test-issuer",
|
||||
Type: "Issuer",
|
||||
},
|
||||
Rules: []standardv1alpha1.Rule{
|
||||
{
|
||||
Name: "myrule1",
|
||||
Backend: &standardv1alpha1.Backend{BackendService: &standardv1alpha1.BackendServiceRef{ServiceName: "test", Port: intstr.FromInt(3030)}},
|
||||
DefaultBackend: &v1alpha1.TypedReference{
|
||||
APIVersion: "k8s.example.com/v1",
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
exp: []*v1beta1.Ingress{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Ingress",
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test-myrule1",
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "contour",
|
||||
"cert-manager.io/issuer": "test-issuer",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
Kind: "Route",
|
||||
Name: "trait-test",
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.IngressSpec{
|
||||
TLS: []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"test.abc"},
|
||||
SecretName: "trait-test-myrule1-cert",
|
||||
},
|
||||
},
|
||||
Backend: &v1beta1.IngressBackend{Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: pointer.StringPtr("k8s.example.com/v1"),
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
}},
|
||||
Rules: []v1beta1.IngressRule{
|
||||
{
|
||||
Host: "test.abc",
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: "",
|
||||
Backend: v1beta1.IngressBackend{ServiceName: "test", ServicePort: intstr.FromInt(3030)},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for message, ti := range tests {
|
||||
contour := &Contour{}
|
||||
got := contour.Construct(ti.routeTrait)
|
||||
assert.Equal(t, len(ti.exp), len(got))
|
||||
for idx := range ti.exp {
|
||||
assert.Equal(t, ti.exp[idx], got[idx], message+" index "+strconv.Itoa(idx))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
cmmeta "github.com/wonderflow/cert-manager-api/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Nginx is nginx ingress implementation
|
||||
type Nginx struct {
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
var _ RouteIngress = &Nginx{}
|
||||
|
||||
// CheckStatus will check status of the ingress
|
||||
func (n *Nginx) CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runtimev1alpha1.Condition) {
|
||||
ctx := context.Background()
|
||||
// check issuer
|
||||
if routeTrait.Spec.TLS != nil && routeTrait.Spec.TLS.Type != standardv1alpha1.ClusterIssuer {
|
||||
tls := routeTrait.Spec.TLS
|
||||
var issuer certmanager.Issuer
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: tls.IssuerName}, &issuer)
|
||||
if err != nil || len(issuer.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("issuer '%v' is pending to be resolved by controller", tls.IssuerName)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
condition := issuer.Status.Conditions[0]
|
||||
if condition.Status != cmmeta.ConditionTrue {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(condition.Reason),
|
||||
Message: condition.Message}}
|
||||
}
|
||||
}
|
||||
// check ingress
|
||||
ingresses := n.Construct(routeTrait)
|
||||
for _, in := range ingresses {
|
||||
|
||||
// Check Certificate
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var cert certmanager.Certificate
|
||||
// check cert
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: in.Name + "-cert"}, &cert)
|
||||
if err != nil || len(cert.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("CertificateRequest %s is pending to be resolved by controller", in.Name)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
certcondition := cert.Status.Conditions[0]
|
||||
if certcondition.Status != cmmeta.ConditionTrue || certcondition.Type != certmanager.CertificateConditionReady {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(certcondition.Reason),
|
||||
Message: certcondition.Message}}
|
||||
}
|
||||
}
|
||||
|
||||
// Check Ingress
|
||||
var ingress v1beta1.Ingress
|
||||
if err := n.Client.Get(ctx, types.NamespacedName{Namespace: in.Namespace, Name: in.Name}, &ingress); err != nil {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: err.Error()}}
|
||||
}
|
||||
ingressvalue := ingress.Status.LoadBalancer.Ingress
|
||||
if len(ingressvalue) < 1 || (ingressvalue[0].IP == "" && ingressvalue[0].Hostname == "") {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonCreating,
|
||||
Message: fmt.Sprintf("IP/Hostname of %s ingress is generating", in.Name)}}
|
||||
}
|
||||
}
|
||||
return StatusReady, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeReady, Status: v1.ConditionTrue,
|
||||
Reason: runtimev1alpha1.ReasonAvailable, LastTransitionTime: metav1.Now()}}
|
||||
}
|
||||
|
||||
// Construct will construct ingress from route
|
||||
func (*Nginx) Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress {
|
||||
|
||||
// Don't create ingress if no host set, this is used for local K8s cluster demo and the route trait will create K8s service only.
|
||||
if routeTrait.Spec.Host == "" || strings.Contains(routeTrait.Spec.Host, "localhost") || strings.Contains(routeTrait.Spec.Host, "127.0.0.1") {
|
||||
return nil
|
||||
}
|
||||
var ingresses []*v1beta1.Ingress
|
||||
for idx, rule := range routeTrait.Spec.Rules {
|
||||
name := rule.Name
|
||||
if name == "" {
|
||||
name = strconv.Itoa(idx)
|
||||
}
|
||||
backend := rule.Backend
|
||||
if backend == nil || backend.BackendService == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var annotations = make(map[string]string)
|
||||
|
||||
annotations["kubernetes.io/ingress.class"] = routeTrait.Spec.IngressClass
|
||||
|
||||
// SSL
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var issuerAnn = "cert-manager.io/issuer"
|
||||
if routeTrait.Spec.TLS.Type == standardv1alpha1.ClusterIssuer {
|
||||
issuerAnn = "cert-manager.io/cluster-issuer"
|
||||
}
|
||||
annotations[issuerAnn] = routeTrait.Spec.TLS.IssuerName
|
||||
}
|
||||
// Rewrite
|
||||
if rule.RewriteTarget != "" {
|
||||
annotations["nginx.ingress.kubernetes.io/rewrite-target"] = rule.RewriteTarget
|
||||
}
|
||||
|
||||
// Custom headers
|
||||
var headerSnippet string
|
||||
for k, v := range rule.CustomHeaders {
|
||||
headerSnippet += fmt.Sprintf("more_set_headers \"%s: %s\";\n", k, v)
|
||||
}
|
||||
if headerSnippet != "" {
|
||||
annotations["nginx.ingress.kubernetes.io/configuration-snippet"] = headerSnippet
|
||||
}
|
||||
|
||||
// Send timeout
|
||||
if backend.SendTimeout != 0 {
|
||||
annotations["nginx.ingress.kubernetes.io/proxy-send-timeout"] = strconv.Itoa(backend.SendTimeout)
|
||||
}
|
||||
|
||||
// Read timeout
|
||||
if backend.ReadTimeout != 0 {
|
||||
annotations["nginx.ingress.kubernetes.io/proxy‑read‑timeout"] = strconv.Itoa(backend.ReadTimeout)
|
||||
}
|
||||
|
||||
ingress := &v1beta1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: routeTrait.Name + "-" + name,
|
||||
Namespace: routeTrait.Namespace,
|
||||
Annotations: annotations,
|
||||
Labels: routeTrait.GetLabels(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: routeTrait.GetUID(),
|
||||
Name: routeTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
ingress.Spec.TLS = []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{routeTrait.Spec.Host},
|
||||
SecretName: routeTrait.Name + "-" + name + "-cert",
|
||||
},
|
||||
}
|
||||
}
|
||||
if rule.DefaultBackend != nil {
|
||||
ingress.Spec.Backend = &v1beta1.IngressBackend{
|
||||
Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: &rule.DefaultBackend.APIVersion,
|
||||
Kind: rule.DefaultBackend.Kind,
|
||||
Name: rule.DefaultBackend.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
ingress.Spec.Rules = []v1beta1.IngressRule{
|
||||
{
|
||||
Host: routeTrait.Spec.Host,
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{
|
||||
Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: rule.Path,
|
||||
Backend: v1beta1.IngressBackend{
|
||||
ServiceName: backend.BackendService.ServiceName,
|
||||
ServicePort: backend.BackendService.Port,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
ingresses = append(ingresses, ingress)
|
||||
}
|
||||
return ingresses
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
func TestConstruct(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
routeTrait *standardv1alpha1.Route
|
||||
exp []*v1beta1.Ingress
|
||||
}{
|
||||
"normal case": {
|
||||
routeTrait: &standardv1alpha1.Route{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Route",
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test",
|
||||
},
|
||||
Spec: standardv1alpha1.RouteSpec{
|
||||
Host: "test.abc",
|
||||
TLS: &standardv1alpha1.TLS{
|
||||
IssuerName: "test-issuer",
|
||||
Type: "Issuer",
|
||||
},
|
||||
Rules: []standardv1alpha1.Rule{
|
||||
{
|
||||
Name: "myrule1",
|
||||
Backend: &standardv1alpha1.Backend{BackendService: &standardv1alpha1.BackendServiceRef{ServiceName: "test", Port: intstr.FromInt(3030)}},
|
||||
DefaultBackend: &v1alpha1.TypedReference{
|
||||
APIVersion: "k8s.example.com/v1",
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
},
|
||||
},
|
||||
},
|
||||
IngressClass: "nginx-private",
|
||||
},
|
||||
},
|
||||
exp: []*v1beta1.Ingress{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Ingress",
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test-myrule1",
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "nginx-private",
|
||||
"cert-manager.io/issuer": "test-issuer",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
Kind: "Route",
|
||||
Name: "trait-test",
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.IngressSpec{
|
||||
TLS: []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"test.abc"},
|
||||
SecretName: "trait-test-myrule1-cert",
|
||||
},
|
||||
},
|
||||
Backend: &v1beta1.IngressBackend{Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: pointer.StringPtr("k8s.example.com/v1"),
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
}},
|
||||
Rules: []v1beta1.IngressRule{
|
||||
{
|
||||
Host: "test.abc",
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: "",
|
||||
Backend: v1beta1.IngressBackend{ServiceName: "test", ServicePort: intstr.FromInt(3030)},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for message, ti := range tests {
|
||||
nginx := &Nginx{}
|
||||
got := nginx.Construct(ti.routeTrait)
|
||||
assert.Equal(t, len(ti.exp), len(got))
|
||||
for idx := range ti.exp {
|
||||
assert.Equal(t, ti.exp[idx], got[idx], message+" index "+strconv.Itoa(idx))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const (
|
||||
errApplyNginxIngress = "failed to apply the ingress"
|
||||
)
|
||||
|
||||
var requeueNotReady = 10 * time.Second
|
||||
|
||||
// Reconciler reconciles a Route object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
Log logr.Logger
|
||||
record event.Recorder
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// Reconcile is the main logic of controller
|
||||
// +kubebuilder:rbac:groups=standard.oam.dev,resources=routes,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=standard.oam.dev,resources=routes/status,verbs=get;update;patch
|
||||
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
ctx := context.Background()
|
||||
mLog := r.Log.WithValues("route", req.NamespacedName)
|
||||
|
||||
mLog.Info("Reconcile route trait")
|
||||
// fetch the trait
|
||||
var routeTrait standardv1alpha1.Route
|
||||
if err := r.Get(ctx, req.NamespacedName, &routeTrait); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
mLog.Info("Get the route trait",
|
||||
"host", routeTrait.Spec.Host,
|
||||
"workload reference", routeTrait.Spec.WorkloadReference,
|
||||
"labels", routeTrait.GetLabels())
|
||||
|
||||
// find the resource object to record the event to, default is the parent appConfig.
|
||||
eventObj, err := oamutil.LocateParentAppConfig(ctx, r.Client, &routeTrait)
|
||||
if eventObj == nil {
|
||||
// fallback to workload itself
|
||||
mLog.Error(err, "add events to route trait itself", "name", routeTrait.Name)
|
||||
eventObj = &routeTrait
|
||||
}
|
||||
|
||||
// Fetch the workload instance to which we want to do routes
|
||||
workload, err := oamutil.FetchWorkload(ctx, r, mLog, &routeTrait)
|
||||
if err != nil {
|
||||
mLog.Error(err, "Error while fetching the workload", "workload reference",
|
||||
routeTrait.GetWorkloadReference())
|
||||
r.record.Event(eventObj, event.Warning(common.ErrLocatingWorkload, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &routeTrait,
|
||||
runtimev1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingWorkload)))
|
||||
}
|
||||
var svc *runtimev1alpha1.TypedReference
|
||||
if NeedDiscovery(&routeTrait) {
|
||||
if svc, err = r.discoveryAndFillBackend(ctx, mLog, eventObj, workload, &routeTrait); err != nil {
|
||||
return oamutil.ReconcileWaitResult, oamutil.PatchCondition(ctx, r, &routeTrait,
|
||||
runtimev1alpha1.ReconcileError(err))
|
||||
}
|
||||
}
|
||||
|
||||
routeIngress, err := ingress.GetRouteIngress(routeTrait.Spec.Provider, r.Client)
|
||||
if err != nil {
|
||||
mLog.Error(err, "Failed to get routeIngress, use nginx route instead")
|
||||
routeIngress = &ingress.Nginx{}
|
||||
}
|
||||
|
||||
// Create Ingress
|
||||
// construct the serviceMonitor that hooks the service to the prometheus server
|
||||
ingresses := routeIngress.Construct(&routeTrait)
|
||||
// server side apply the serviceMonitor, only the fields we set are touched
|
||||
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner(routeTrait.GetUID())}
|
||||
for _, ingress := range ingresses {
|
||||
if err := r.Patch(ctx, ingress, client.Apply, applyOpts...); err != nil {
|
||||
mLog.Error(err, "Failed to apply to ingress")
|
||||
r.record.Event(eventObj, event.Warning(errApplyNginxIngress, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &routeTrait,
|
||||
runtimev1alpha1.ReconcileError(errors.Wrap(err, errApplyNginxIngress)))
|
||||
}
|
||||
r.record.Event(eventObj, event.Normal("nginx ingress patched",
|
||||
fmt.Sprintf("successfully server side patched a route trait `%s`", routeTrait.Name)))
|
||||
}
|
||||
// TODO(wonderflow): GC mechanism for no used ingress, service, issuer
|
||||
|
||||
var ingressCreated []runtimev1alpha1.TypedReference
|
||||
for _, ingress := range ingresses {
|
||||
ingressCreated = append(ingressCreated, runtimev1alpha1.TypedReference{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
|
||||
Name: ingress.Name,
|
||||
UID: routeTrait.UID,
|
||||
})
|
||||
}
|
||||
routeTrait.Status.Ingresses = ingressCreated
|
||||
routeTrait.Status.Service = svc
|
||||
var conditions []runtimev1alpha1.Condition
|
||||
routeTrait.Status.Status, conditions = routeIngress.CheckStatus(&routeTrait)
|
||||
routeTrait.Status.Conditions = conditions
|
||||
if routeTrait.Status.Status != ingress.StatusReady {
|
||||
return ctrl.Result{RequeueAfter: requeueNotReady}, r.UpdateStatus(ctx, &routeTrait)
|
||||
}
|
||||
err = r.UpdateStatus(ctx, &routeTrait)
|
||||
if err != nil {
|
||||
return oamutil.ReconcileWaitResult, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// discoveryAndFillBackend will automatically discovery backend for route
|
||||
func (r *Reconciler) discoveryAndFillBackend(ctx context.Context, mLog logr.Logger, eventObj runtime.Object, workload *unstructured.Unstructured,
|
||||
routeTrait *standardv1alpha1.Route) (*runtimev1alpha1.TypedReference, error) {
|
||||
|
||||
// Fetch the child childResources list from the corresponding workload
|
||||
childResources, err := oamutil.FetchWorkloadChildResources(ctx, mLog, r, r.dm, workload)
|
||||
if err != nil {
|
||||
mLog.Error(err, "Error while fetching the workload child childResources", "workload kind", workload.GetKind(),
|
||||
"workload name", workload.GetName())
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// try to see if the workload already has services in child childResources, and match for our route
|
||||
r.fillBackendByCheckChildResource(mLog, routeTrait, childResources)
|
||||
|
||||
// Check if still need discovery after childResource filled.
|
||||
if NeedDiscovery(routeTrait) {
|
||||
// no service found, we will create service according to rule
|
||||
svc, err := r.fillBackendByCreatedService(ctx, mLog, workload, routeTrait, childResources)
|
||||
if err != nil {
|
||||
r.record.Event(eventObj, event.Warning(common.ErrCreatingService, err))
|
||||
return nil, errors.Wrap(err, common.ErrCreatingService)
|
||||
}
|
||||
r.record.Event(eventObj, event.Normal("Service created",
|
||||
fmt.Sprintf("successfully automatically created a service `%s`", svc.Name)))
|
||||
return svc, nil
|
||||
}
|
||||
mLog.Info("workload already has service as child resource, will not create service", "workloadName", workload.GetName())
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
// fillBackendByCreatedService will automatically create service by discovery podTemplate or podSpec.
|
||||
func (r *Reconciler) fillBackendByCreatedService(ctx context.Context, mLog logr.Logger, workload *unstructured.Unstructured,
|
||||
routeTrait *standardv1alpha1.Route, childResources []*unstructured.Unstructured) (*runtimev1alpha1.TypedReference, error) {
|
||||
|
||||
oamService := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: common.ServiceKind,
|
||||
APIVersion: common.ServiceAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: routeTrait.GetName(),
|
||||
Namespace: routeTrait.GetNamespace(),
|
||||
Labels: utils.SelectOAMAppLabelsWithoutRevision(routeTrait.GetLabels()),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: routeTrait.GetUID(),
|
||||
Name: routeTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
},
|
||||
}
|
||||
|
||||
ports, labels, err := DiscoverPortsLabel(ctx, workload, r, r.dm, childResources)
|
||||
if err != nil {
|
||||
mLog.Info("[WARN] fail to discovery port and label", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
oamService.Spec.Selector = labels
|
||||
|
||||
// use the same port
|
||||
for _, port := range ports {
|
||||
oamService.Spec.Ports = append(oamService.Spec.Ports, corev1.ServicePort{
|
||||
Port: int32(port.IntValue()),
|
||||
TargetPort: port,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
})
|
||||
}
|
||||
// server side apply the service, only the fields we set are touched
|
||||
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner(routeTrait.GetUID())}
|
||||
if err := r.Patch(ctx, oamService, client.Apply, applyOpts...); err != nil {
|
||||
mLog.Error(err, "Failed to apply to service")
|
||||
return nil, err
|
||||
}
|
||||
FillRouteTraitWithService(oamService, routeTrait)
|
||||
return &runtimev1alpha1.TypedReference{
|
||||
APIVersion: common.ServiceAPIVersion,
|
||||
Kind: common.ServiceKind,
|
||||
Name: oamService.Name,
|
||||
UID: routeTrait.UID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates standardv1alpha1.Route's Status with retry.RetryOnConflict
|
||||
func (r *Reconciler) UpdateStatus(ctx context.Context, route *standardv1alpha1.Route, opts ...client.UpdateOption) error {
|
||||
status := route.DeepCopy().Status
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
if err = r.Get(ctx, types.NamespacedName{Namespace: route.Namespace, Name: route.Name}, route); err != nil {
|
||||
return
|
||||
}
|
||||
route.Status = status
|
||||
return r.Status().Update(ctx, route, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
// DiscoverPortsLabel assume the workload or it's childResource will always having spec.template as PodTemplate if discoverable
|
||||
func DiscoverPortsLabel(ctx context.Context, workload *unstructured.Unstructured, r client.Reader, dm discoverymapper.DiscoveryMapper, childResources []*unstructured.Unstructured) ([]intstr.IntOrString, map[string]string, error) {
|
||||
|
||||
// here is the logic follows the design https://github.com/crossplane/oam-kubernetes-runtime/blob/master/design/one-pager-podspecable-workload.md#proposal
|
||||
// Get WorkloadDefinition
|
||||
workloadDef, err := oamutil.FetchWorkloadDefinition(ctx, r, dm, workload)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
podSpecPath, ok := utils.GetPodSpecPath(workloadDef)
|
||||
if podSpecPath != "" {
|
||||
ports, err := utils.DiscoveryFromPodSpec(workload, podSpecPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return ports, utils.SelectOAMAppLabelsWithoutRevision(workload.GetLabels()), nil
|
||||
}
|
||||
if ok {
|
||||
return utils.DiscoveryFromPodTemplate(workload, "spec", "template")
|
||||
}
|
||||
|
||||
// If workload is not podSpecable, try to detect it's child resource
|
||||
var resources = []*unstructured.Unstructured{workload}
|
||||
resources = append(resources, childResources...)
|
||||
var gatherErrs []error
|
||||
for _, w := range resources {
|
||||
port, labels, err := utils.DiscoveryFromPodTemplate(w, "spec", "template")
|
||||
if err == nil {
|
||||
return port, labels, nil
|
||||
}
|
||||
gatherErrs = append(gatherErrs, err)
|
||||
}
|
||||
return nil, nil, fmt.Errorf("fail to automatically discovery backend from workload %v(%v.%v) and it's child resource, errorList: %v", workload.GetName(), workload.GetAPIVersion(), workload.GetKind(), gatherErrs)
|
||||
}
|
||||
|
||||
// fetch the service that is associated with the workload
|
||||
func (r *Reconciler) fillBackendByCheckChildResource(mLog logr.Logger,
|
||||
routeTrait *standardv1alpha1.Route, childResources []*unstructured.Unstructured) {
|
||||
if len(childResources) == 0 {
|
||||
return
|
||||
}
|
||||
// find the service that has the port
|
||||
for _, childRes := range childResources {
|
||||
if childRes.GetAPIVersion() == corev1.SchemeGroupVersion.String() && childRes.GetKind() == reflect.TypeOf(corev1.Service{}).Name() {
|
||||
data, err := json.Marshal(childRes.Object)
|
||||
if err != nil {
|
||||
mLog.Error(err, "error marshal child childResources as K8s Service, continue to check other resource", "resource name", childRes.GetName())
|
||||
continue
|
||||
}
|
||||
var service corev1.Service
|
||||
err = json.Unmarshal(data, &service)
|
||||
if err != nil {
|
||||
mLog.Error(err, "error unmarshal child childResources as K8s Service, continue to check other resource", "resource name", childRes.GetName())
|
||||
continue
|
||||
}
|
||||
FillRouteTraitWithService(&service, routeTrait)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetupWithManager setup with manager
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("Route")).
|
||||
WithAnnotations("controller", "route")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&standardv1alpha1.Route{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// Setup adds a controller that reconciles MetricsTrait.
|
||||
func Setup(mgr ctrl.Manager) error {
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reconciler := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("Route"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
}
|
||||
return reconciler.SetupWithManager(mgr)
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Route Trait Integration Test", func() {
|
||||
// common var init
|
||||
ctx := context.Background()
|
||||
namespaceName := "routetrait-integration-test"
|
||||
|
||||
podPort := 8000
|
||||
issuerName := "my-issuer"
|
||||
|
||||
var ns corev1.Namespace
|
||||
getComponent := func(workloadType, compName string) (v1alpha2.Component, map[string]string, map[string]string) {
|
||||
podTemplateLabel := map[string]string{"standard.oam.dev": "oam-test-deployment", "workload.oam.dev/type": workloadType}
|
||||
workloadLabel := map[string]string{"standard.oam.dev": "oam-test-deployment", "app.oam.dev/component": compName, "app.oam.dev/name": "test-app-" + compName}
|
||||
basedeploy := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: podTemplateLabel,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: podTemplateLabel,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: podTemplateLabel,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container-name",
|
||||
Image: "crccheck/hello-world",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(podPort),
|
||||
}}}}}}},
|
||||
}
|
||||
var rp = int32(1)
|
||||
if workloadType == "webservice" {
|
||||
basePodSpecc := &v1alpha1.PodSpecWorkload{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PodSpecWorkload",
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: podTemplateLabel,
|
||||
},
|
||||
Spec: v1alpha1.PodSpecWorkloadSpec{
|
||||
Replicas: &rp,
|
||||
PodSpec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container-name",
|
||||
Image: "crccheck/hello-world",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(podPort),
|
||||
}}}}}},
|
||||
}
|
||||
return v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: ns.Name,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{Object: basePodSpecc},
|
||||
},
|
||||
}, workloadLabel, podTemplateLabel
|
||||
}
|
||||
return v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: ns.Name,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{Object: basedeploy},
|
||||
},
|
||||
}, workloadLabel, podTemplateLabel
|
||||
}
|
||||
|
||||
getAC := func(compName string) v1alpha2.ApplicationConfiguration {
|
||||
return v1alpha2.ApplicationConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ApplicationConfiguration",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns.Name,
|
||||
Name: "test-app-" + compName,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: runtime.RawExtension{Object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "standard.oam.dev/v1alpha1",
|
||||
"kind": "Route",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
oam.TraitTypeLabel: "route",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"host": "mycomp.mytest.com",
|
||||
"tls": map[string]interface{}{
|
||||
"issuerName": issuerName,
|
||||
}}}}}}}}}}}
|
||||
}
|
||||
BeforeEach(func() {
|
||||
logf.Log.Info("[TEST] Set up resources before an integration test")
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespaceName,
|
||||
},
|
||||
}
|
||||
|
||||
By("Create the Namespace for test")
|
||||
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
By("Create the Issuer for test")
|
||||
Expect(k8sClient.Create(context.Background(), &certmanager.Issuer{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: issuerName, Namespace: namespaceName},
|
||||
Spec: certmanager.IssuerSpec{IssuerConfig: certmanager.IssuerConfig{SelfSigned: &certmanager.SelfSignedIssuer{}}},
|
||||
})).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Control-runtime test environment has a bug that can't delete resources like deployment/namespaces
|
||||
// We have to use different names to segregate between tests
|
||||
logf.Log.Info("[TEST] Clean up resources after an integration test")
|
||||
})
|
||||
|
||||
It("Test with child resource no podSpecable but has service child using webservice workload", func() {
|
||||
compName := "test-webservice"
|
||||
comp, _, _ := getComponent("webservice", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() error {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return errors.New("workload or trait not ready")
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(createdRoute.Status.Ingresses) == 0 {
|
||||
return errors.New("no ingress created")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
|
||||
By("Check that we have created the ingress")
|
||||
createdIngress := v1beta1.Ingress{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: createdRoute.Status.Ingresses[0].Name},
|
||||
&createdIngress)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created ingress", "ingress rules", createdIngress.Spec.Rules)
|
||||
Expect(createdIngress.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(len(createdIngress.Spec.Rules)).Should(Equal(1))
|
||||
Expect(createdIngress.Spec.Rules[0].Host).Should(Equal("mycomp.mytest.com"))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName).Should(Equal(compName))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal).Should(Equal(int32(8080)))
|
||||
})
|
||||
It("Test with podSpec label with no podSpecPath using deployment workload", func() {
|
||||
compName := "test-deployment"
|
||||
comp, _, deploylabel := getComponent("deployment", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() error {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return errors.New("workload or trait not ready")
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(createdRoute.Status.Ingresses) == 0 {
|
||||
return errors.New("no ingress created")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
|
||||
By("Check that we have created the ingress")
|
||||
createdIngress := v1beta1.Ingress{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: createdRoute.Status.Ingresses[0].Name},
|
||||
&createdIngress)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created ingress", "ingress rules", createdIngress.Spec.Rules)
|
||||
Expect(createdIngress.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(len(createdIngress.Spec.Rules)).Should(Equal(1))
|
||||
Expect(createdIngress.Spec.Rules[0].Host).Should(Equal("mycomp.mytest.com"))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName).Should(Equal(traitName))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal).Should(Equal(int32(8000)))
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdSvc := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdSvc)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdSvc.Spec.Ports)
|
||||
Expect(createdSvc.Spec.Selector).Should(Equal(deploylabel))
|
||||
Expect(createdSvc.Spec.Ports[0].TargetPort.IntVal).Should(Equal(int32(podPort)))
|
||||
})
|
||||
It("Test with podSpecPath specified using deploy workload", func() {
|
||||
compName := "test-deploy"
|
||||
comp, _, _ := getComponent("deploy", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() error {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return errors.New("workload or trait not ready")
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(createdRoute.Status.Ingresses) == 0 {
|
||||
return errors.New("no ingress created")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
|
||||
By("Check that we have created the ingress")
|
||||
createdIngress := v1beta1.Ingress{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: createdRoute.Status.Ingresses[0].Name},
|
||||
&createdIngress)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created ingress", "ingress rules", createdIngress.Spec.Rules)
|
||||
Expect(createdIngress.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(len(createdIngress.Spec.Rules)).Should(Equal(1))
|
||||
Expect(createdIngress.Spec.Rules[0].Host).Should(Equal("mycomp.mytest.com"))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName).Should(Equal(traitName))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal).Should(Equal(int32(8000)))
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdSvc := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdSvc)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdSvc.Spec.Ports)
|
||||
for k, v := range map[string]string{"app.oam.dev/component": compName, "app.oam.dev/name": "test-app-" + compName} {
|
||||
Expect(createdSvc.Spec.Selector).Should(HaveKeyWithValue(k, v))
|
||||
}
|
||||
Expect(createdSvc.Spec.Ports[0].TargetPort.IntVal).Should(Equal(int32(podPort)))
|
||||
})
|
||||
It("Test should get error condition if definition not found", func() {
|
||||
compName := "test-no-def"
|
||||
comp, _, _ := getComponent("unknow1", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() string {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return "workload or trait not ready"
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(createdRoute.Status.Conditions) == 1 {
|
||||
return createdRoute.Status.Conditions[0].Message
|
||||
}
|
||||
return ""
|
||||
},
|
||||
time.Second*10, time.Millisecond*500).Should(Equal(`failed to create the services: WorkloadDefinition.core.oam.dev "unknow1" not found`))
|
||||
})
|
||||
})
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
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 routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration"
|
||||
|
||||
controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
|
||||
oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var controllerDone chan struct{}
|
||||
var routeNS corev1.Namespace
|
||||
|
||||
var RouteNSName = "route-test"
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
routeNS = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: RouteNSName,
|
||||
},
|
||||
}
|
||||
By("Bootstrapping test environment")
|
||||
useExistCluster := false
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("../../../../..", "charts/vela-core/crds"), // this has all the required CRDs,
|
||||
},
|
||||
UseExistingCluster: &useExistCluster,
|
||||
}
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
Expect(standardv1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred())
|
||||
Expect(oamCore.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred())
|
||||
Expect(certmanager.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
By("Create the k8s client")
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
By("Starting the route trait controller in the background")
|
||||
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
Port: 9443,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
r := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("RouteTrait"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
Expect(applicationconfiguration.Setup(mgr, controller.Args{}, logging.NewLogrLogger(ctrl.Log.WithName("AppConfig")))).ToNot(HaveOccurred())
|
||||
Expect(podspecworkload.Setup(mgr)).ToNot(HaveOccurred())
|
||||
|
||||
controllerDone = make(chan struct{}, 1)
|
||||
// +kubebuilder:scaffold:builder
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
Expect(mgr.Start(controllerDone)).ToNot(HaveOccurred())
|
||||
}()
|
||||
|
||||
By("Create the routeTrait namespace")
|
||||
Expect(k8sClient.Create(context.Background(), &routeNS)).ToNot(HaveOccurred())
|
||||
routeDef := &v1alpha2.TraitDefinition{}
|
||||
routeDef.Name = "route"
|
||||
routeDef.Namespace = RouteNSName
|
||||
routeDef.Spec.Reference.Name = "routes.standard.oam.dev"
|
||||
routeDef.Spec.WorkloadRefPath = "spec.workloadRef"
|
||||
Expect(k8sClient.Create(context.Background(), routeDef)).ToNot(HaveOccurred())
|
||||
|
||||
webservice := &v1alpha2.WorkloadDefinition{}
|
||||
webservice.Name = "webservice"
|
||||
webservice.Namespace = RouteNSName
|
||||
webservice.Spec.Reference.Name = "deployments.apps"
|
||||
webservice.Spec.ChildResourceKinds = []v1alpha2.ChildResourceKind{{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
}, {
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
}}
|
||||
Expect(k8sClient.Create(context.Background(), webservice)).ToNot(HaveOccurred())
|
||||
|
||||
deployment := &v1alpha2.WorkloadDefinition{}
|
||||
deployment.Name = "deployment"
|
||||
deployment.Namespace = RouteNSName
|
||||
deployment.Labels = map[string]string{"workload.oam.dev/podspecable": "true"}
|
||||
deployment.Spec.Reference.Name = "deployments.apps"
|
||||
Expect(k8sClient.Create(context.Background(), deployment)).ToNot(HaveOccurred())
|
||||
|
||||
deploy := &v1alpha2.WorkloadDefinition{}
|
||||
deploy.Name = "deploy"
|
||||
deploy.Namespace = RouteNSName
|
||||
deploy.Spec.PodSpecPath = "spec.template.spec"
|
||||
deploy.Spec.Reference.Name = "deployments.apps"
|
||||
Expect(k8sClient.Create(context.Background(), deploy)).ToNot(HaveOccurred())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("Stop the routeTrait controller")
|
||||
close(controllerDone)
|
||||
By("Delete the route-test namespace")
|
||||
Expect(k8sClient.Delete(context.Background(), &routeNS,
|
||||
client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
|
||||
By("Tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
@@ -1,72 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// NeedDiscovery checks the routeTrait Spec if it's needed to automatically discover
|
||||
func NeedDiscovery(routeTrait *v1alpha1.Route) bool {
|
||||
if len(routeTrait.Spec.Rules) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, rule := range routeTrait.Spec.Rules {
|
||||
if rule.Backend == nil {
|
||||
return true
|
||||
}
|
||||
if rule.Backend.BackendService == nil {
|
||||
return true
|
||||
}
|
||||
if rule.Backend.BackendService.ServiceName == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchService try check if the service matches the rules
|
||||
func MatchService(targetPort intstr.IntOrString, rule v1alpha1.Rule) bool {
|
||||
// the rule is nil, continue
|
||||
if rule.Backend == nil || rule.Backend.BackendService == nil || rule.Backend.BackendService.Port.IntValue() == 0 {
|
||||
return true
|
||||
}
|
||||
if rule.Backend.BackendService.ServiceName != "" {
|
||||
return false
|
||||
}
|
||||
// the rule is not null, if any port matches, we regard them are all match
|
||||
if targetPort == rule.Backend.BackendService.Port {
|
||||
return true
|
||||
}
|
||||
// port is not matched, mark it not match
|
||||
return false
|
||||
}
|
||||
|
||||
// FillRouteTraitWithService will use existing Service or created Service to fill the spec
|
||||
func FillRouteTraitWithService(service *corev1.Service, routeTrait *v1alpha1.Route) {
|
||||
if len(routeTrait.Spec.Rules) == 0 {
|
||||
routeTrait.Spec.Rules = []v1alpha1.Rule{{Name: "auto-created"}}
|
||||
}
|
||||
for idx, rule := range routeTrait.Spec.Rules {
|
||||
// If backendService.port not specified, will always use the service found and it's first port as backendService.
|
||||
for _, servicePort := range service.Spec.Ports {
|
||||
// We use targetPort rather than port to match with the rule, because if serviceName not specified,
|
||||
// Users will only know containerPort(which is targetPort)
|
||||
if MatchService(servicePort.TargetPort, rule) {
|
||||
ref := &v1alpha1.BackendServiceRef{
|
||||
// Use port of service rather than targetPort, it will be used in ingress pointing to the service
|
||||
Port: intstr.FromInt(int(servicePort.Port)),
|
||||
ServiceName: service.Name,
|
||||
}
|
||||
if rule.Backend == nil {
|
||||
rule.Backend = &v1alpha1.Backend{BackendService: ref}
|
||||
} else {
|
||||
rule.Backend.BackendService = ref
|
||||
}
|
||||
routeTrait.Spec.Rules[idx] = rule
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user