Remove 3 standard controller (route autoscaler metrics ) (#1172)

* delete 4 standard controllers related  code

delete related yaml

delete setup controller

add back podspecworkload controller

try to fix e2e

update related docs

fix failed test

fix docs problem

remove useless scheme

up timeout for e2e-test

change doc  structure

* fix go mod
This commit is contained in:
wyike
2021-03-12 14:57:59 +08:00
committed by GitHub
parent cc044b0de5
commit ccc5826616
46 changed files with 117 additions and 31746 deletions

View File

@@ -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

View File

@@ -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{})
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
@@ -672,228 +427,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
}

View File

@@ -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: []

View File

@@ -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: []

View File

@@ -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: []

View File

@@ -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"
@@ -59,11 +57,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
}

View File

@@ -1,10 +1,31 @@
# 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
Autoscale depends on metrics server, please [enable it in your Kubernetes cluster](../references/devex/faq.md#autoscale-how-to-enable-metrics-server-in-various-kubernetes-clusters) at the beginning.
> 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`.
Autoscale depends on metrics server, please [enable it in your Kubernetes cluster](../references/devex/faq.md#autoscale-how-to-enable-metrics-server-in-various-kubernetes-clusters) at the beginning.
## Setting cron auto-scaling policy
Introduce how to automatically scale workloads by cron.

View File

@@ -1,14 +1,38 @@
# Monitoring Application
If your application has exposed metrics, you can easily tell the platform how to collect the metrics data from your app with `metrics` capability.
## 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`.
If your application has exposed metrics, you can easily tell the platform how to collect the metrics data from your app with `metrics` capability.
## Setting metrics policy
Let's run [`christianhxc/gorandom:1.0`](https://github.com/christianhxc/prometheus-tutorial) as an example app.
The app will emit random latencies as metrics.
1. Prepare Appfile:
```bash

View File

@@ -1,11 +1,31 @@
# Setting Routes
> 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.
## Prerequisite
Make sure route trait controller is installed in your cluster
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`.
## Setting route policy
Add routing config under `express-server`:
```yaml

1
go.mod
View File

@@ -52,7 +52,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

856
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -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: []

View File

@@ -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: []

View File

@@ -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: []

View File

@@ -17,7 +17,6 @@ import (
"k8s.io/apimachinery/pkg/types"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/oam"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
@@ -133,10 +132,10 @@ var _ = Describe("Test Application apply", func() {
appConfig.Spec.Components[0].Traits = []v1alpha2.ComponentTrait{
{
Trait: runtime.RawExtension{
Object: &v1alpha1.MetricsTrait{
Object: &v1alpha2.ManualScalerTrait{
TypeMeta: metav1.TypeMeta{
Kind: "MetricsTrait",
APIVersion: "standard.oam.dev/v1alpha1",
Kind: "ManualScalerTrait",
APIVersion: "core.oam.dev/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: app.Name,
@@ -278,7 +277,7 @@ var _ = Describe("Test Application apply", func() {
appConfig.Spec.Components[0].Traits = []v1alpha2.ComponentTrait{
{
Trait: runtime.RawExtension{
Object: &v1alpha1.MetricsTrait{
Object: &v1alpha2.ManualScalerTrait{
TypeMeta: metav1.TypeMeta{
Kind: "MetricsTrait",
APIVersion: "standard.oam.dev/v1alpha1",

View File

@@ -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 {

View File

@@ -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 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)
ctx = util.SetNamespaceInCtx(ctx, scaler.Namespace)
// 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 Auto-Scale Trait.
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)
}

View File

@@ -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
}

View File

@@ -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())
})

View File

@@ -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"
)

View File

@@ -1,368 +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())
ctx = oamutil.SetNamespaceInCtx(ctx, metricsTrait.Namespace)
// 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)
}

View File

@@ -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))
})
})

View File

@@ -1,147 +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"
"os"
"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")
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 metrics test", "yaml_path", yamlPath)
useExistCluster := false
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{
yamlPath, // this has all the required oam CRDs,
filepath.Join("..", "testdata/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())
})

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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))
}
}
}

View File

@@ -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/proxyreadtimeout"] = 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
}

View File

@@ -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))
}
}
}

View File

@@ -1,347 +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())
ctx = oamutil.SetNamespaceInCtx(ctx, routeTrait.Namespace)
// 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)
}

View File

@@ -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`))
})
})

View File

@@ -1,182 +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"
"os"
"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")
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 route suit_test", "yaml_path", yamlPath)
useExistCluster := false
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{
yamlPath, // this has all the required CRDs,
filepath.Join("..", "testdata/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("create definition namespace vela-system")
ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}}
Expect(k8sClient.Create(context.Background(), &ns)).Should(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 = "vela-system"
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 = "vela-system"
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 = "vela-system"
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 = "vela-system"
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())
})

View File

@@ -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
}
}
}
}

View File

@@ -1 +0,0 @@
package routes

File diff suppressed because it is too large Load Diff

View File

@@ -1,372 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.1
creationTimestamp: null
name: servicemonitors.monitoring.coreos.com
spec:
group: monitoring.coreos.com
names:
kind: ServiceMonitor
listKind: ServiceMonitorList
plural: servicemonitors
singular: servicemonitor
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: ServiceMonitor defines monitoring for a set of services.
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: Specification of desired Service selection for target discovery by Prometheus.
properties:
endpoints:
description: A list of endpoints allowed as part of this ServiceMonitor.
items:
description: Endpoint defines a scrapeable endpoint serving Prometheus metrics.
properties:
basicAuth:
description: 'BasicAuth allow an endpoint to authenticate over basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints'
properties:
password:
description: The secret in the service monitor namespace that contains the password for authentication.
properties:
key:
description: The key of the secret to select from. Must be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
username:
description: The secret in the service monitor namespace that contains the username for authentication.
properties:
key:
description: The key of the secret to select from. Must be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
type: object
bearerTokenFile:
description: File to read bearer token for scraping targets.
type: string
bearerTokenSecret:
description: Secret to mount to read bearer token for scraping targets. The secret needs to be in the same namespace as the service monitor and accessible by the Prometheus Operator.
properties:
key:
description: The key of the secret to select from. Must be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
honorLabels:
description: HonorLabels chooses the metric's labels on collisions with target labels.
type: boolean
honorTimestamps:
description: HonorTimestamps controls whether Prometheus respects the timestamps present in scraped data.
type: boolean
interval:
description: Interval at which metrics should be scraped
type: string
metricRelabelings:
description: MetricRelabelConfigs to apply to samples before ingestion.
items:
description: 'RelabelConfig allows dynamic rewriting of the label set, being applied to samples before ingestion. It defines `<metric_relabel_configs>`-section of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching. Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex replace is performed if the regular expression matches. Regex capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing labels. Their content is concatenated using the configured separator and matched against the configured regular expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written in a replace action. It is mandatory for replace actions. Regex capture groups are available.
type: string
type: object
type: array
params:
additionalProperties:
items:
type: string
type: array
description: Optional HTTP URL parameters
type: object
path:
description: HTTP path to scrape for metrics.
type: string
port:
description: Name of the service port this endpoint refers to. Mutually exclusive with targetPort.
type: string
proxyUrl:
description: ProxyURL eg http://proxyserver:2195 Directs scrapes to proxy through this endpoint.
type: string
relabelings:
description: 'RelabelConfigs to apply to samples before scraping. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'
items:
description: 'RelabelConfig allows dynamic rewriting of the label set, being applied to samples before ingestion. It defines `<metric_relabel_configs>`-section of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching. Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex replace is performed if the regular expression matches. Regex capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing labels. Their content is concatenated using the configured separator and matched against the configured regular expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written in a replace action. It is mandatory for replace actions. Regex capture groups are available.
type: string
type: object
type: array
scheme:
description: HTTP scheme to use for scraping.
type: string
scrapeTimeout:
description: Timeout after which the scrape is ended
type: string
targetPort:
anyOf:
- type: integer
- type: string
description: Name or number of the target port of the Pod behind the Service, the port must be specified with container port property. Mutually exclusive with port.
x-kubernetes-int-or-string: true
tlsConfig:
description: TLS configuration to use when scraping the endpoint
properties:
ca:
description: Struct containing the CA cert to use for the targets.
properties:
configMap:
description: ConfigMap containing data to use for the targets.
properties:
key:
description: The key to select.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the ConfigMap or its key must be defined
type: boolean
required:
- key
type: object
secret:
description: Secret containing data to use for the targets.
properties:
key:
description: The key of the secret to select from. Must be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
type: object
caFile:
description: Path to the CA cert in the Prometheus container to use for the targets.
type: string
cert:
description: Struct containing the client cert file for the targets.
properties:
configMap:
description: ConfigMap containing data to use for the targets.
properties:
key:
description: The key to select.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the ConfigMap or its key must be defined
type: boolean
required:
- key
type: object
secret:
description: Secret containing data to use for the targets.
properties:
key:
description: The key of the secret to select from. Must be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
type: object
certFile:
description: Path to the client cert file in the Prometheus container for the targets.
type: string
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: Path to the client key file in the Prometheus container for the targets.
type: string
keySecret:
description: Secret containing the client key file for the targets.
properties:
key:
description: The key of the secret to select from. Must be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string
type: object
type: object
type: array
jobLabel:
description: The label to use to retrieve the job name from.
type: string
namespaceSelector:
description: Selector to select which namespaces the Endpoints objects are discovered from.
properties:
any:
description: Boolean describing whether all namespaces are selected in contrast to a list restricting them.
type: boolean
matchNames:
description: List of namespace names.
items:
type: string
type: array
type: object
podTargetLabels:
description: PodTargetLabels transfers labels on the Kubernetes Pod onto the target.
items:
type: string
type: array
sampleLimit:
description: SampleLimit defines per-scrape limit on number of scraped samples that will be accepted.
format: int64
type: integer
selector:
description: Selector to select Endpoints objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
targetLabels:
description: TargetLabels transfers labels on the Kubernetes Service onto the target.
items:
type: string
type: array
targetLimit:
description: TargetLimit defines a limit on the number of scraped targets that will be accepted.
format: int64
type: integer
required:
- endpoints
- selector
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -69,7 +69,7 @@ func (def *CapabilityWorkloadDefinition) GetCapabilityObject(ctx context.Context
}
err := k8sClient.Get(ctx, objectKey, &workloadDefinition)
if err != nil {
return nil, fmt.Errorf("failed to get WorkloadDefinition %s: %v", def.Name, err)
return nil, fmt.Errorf("failed to get WorkloadDefinition %s: %w", def.Name, err)
}
def.WorkloadDefinition = workloadDefinition
capability, err = util.ConvertTemplateJSON2Object(name, workloadDefinition.Spec.Extension, workloadDefinition.Spec.Schematic)
@@ -92,7 +92,7 @@ func (def *CapabilityWorkloadDefinition) GetOpenAPISchema(ctx context.Context, k
func (def *CapabilityWorkloadDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name string) error {
jsonSchema, err := def.GetOpenAPISchema(ctx, k8sClient, namespace, name)
if err != nil {
return fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %v", def.Name, err)
return fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err)
}
workloadDefinition := def.WorkloadDefinition
ownerReference := []metav1.OwnerReference{{
@@ -124,7 +124,7 @@ func (def *CapabilityTraitDefinition) GetCapabilityObject(ctx context.Context, k
}
err := k8sClient.Get(ctx, objectKey, &traitDefinition)
if err != nil {
return &capability, fmt.Errorf("failed to get WorkloadDefinition %s: %v", def.Name, err)
return &capability, fmt.Errorf("failed to get WorkloadDefinition %s: %w", def.Name, err)
}
def.TraitDefinition = traitDefinition
capability, err = util.ConvertTemplateJSON2Object(name, traitDefinition.Spec.Extension, traitDefinition.Spec.Schematic)

View File

@@ -6,7 +6,6 @@ import (
"github.com/oam-dev/kubevela/pkg/controller/common"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/webhook/standard.oam.dev/v1alpha1/metrics"
"github.com/oam-dev/kubevela/pkg/webhook/standard.oam.dev/v1alpha1/podspecworkload"
)
@@ -19,13 +18,6 @@ import (
func Register(mgr manager.Manager, disableCaps string) {
disableCapsSet := utils.StoreInSet(disableCaps)
server := mgr.GetWebhookServer()
if disableCaps == common.DisableNoneCaps || !disableCapsSet.Contains(common.MetricsControllerName) {
// MetricsTrait
server.Register("/validate-standard-oam-dev-v1alpha1-metricstrait",
&webhook.Admission{Handler: &metrics.ValidatingHandler{}})
server.Register("/mutate-standard-oam-dev-v1alpha1-metricstrait",
&webhook.Admission{Handler: &metrics.MutatingHandler{}})
}
if disableCaps == common.DisableNoneCaps || !disableCapsSet.Contains(common.PodspecWorkloadControllerName) {
// PodSpecWorkload
server.Register("/validate-standard-oam-dev-v1alpha1-podspecworkload",

View File

@@ -1,86 +0,0 @@
package metrics
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
)
func TestMetrics(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Metrics Suite")
}
var _ = Describe("Metrics Admission controller Test", func() {
var traitBase v1alpha1.MetricsTrait
BeforeEach(func() {
traitBase = v1alpha1.MetricsTrait{
ObjectMeta: metav1.ObjectMeta{
Name: "mutate-hook",
Namespace: "default",
},
Spec: v1alpha1.MetricsTraitSpec{
ScrapeService: v1alpha1.ScapeServiceEndPoint{
TargetPort: intstr.FromInt(1234),
},
},
}
})
It("Test with fill in all default", func() {
trait := traitBase
want := traitBase
want.Spec.ScrapeService.Format = SupportedFormat
want.Spec.ScrapeService.Scheme = SupportedScheme
want.Spec.ScrapeService.Path = DefaultMetricsPath
want.Spec.ScrapeService.Enabled = pointer.BoolPtr(true)
DefaultMetrics(&trait)
Expect(trait).Should(BeEquivalentTo(want))
})
It("Test only fill in empty fields", func() {
trait := traitBase
trait.Spec.ScrapeService.Path = "not default"
want := trait
want.Spec.ScrapeService.Format = SupportedFormat
want.Spec.ScrapeService.Scheme = SupportedScheme
want.Spec.ScrapeService.Enabled = pointer.BoolPtr(true)
DefaultMetrics(&trait)
Expect(trait).Should(BeEquivalentTo(want))
})
It("Test not fill in enabled field", func() {
trait := traitBase
trait.Spec.ScrapeService.Enabled = pointer.BoolPtr(false)
want := trait
want.Spec.ScrapeService.Format = SupportedFormat
want.Spec.ScrapeService.Scheme = SupportedScheme
want.Spec.ScrapeService.Path = DefaultMetricsPath
want.Spec.ScrapeService.Enabled = pointer.BoolPtr(false)
DefaultMetrics(&trait)
Expect(trait).Should(BeEquivalentTo(want))
})
It("Test validate valid trait", func() {
trait := traitBase
trait.Spec.ScrapeService.Format = SupportedFormat
trait.Spec.ScrapeService.Scheme = SupportedScheme
Expect(ValidateCreate(&trait).ToAggregate()).NotTo(HaveOccurred())
Expect(ValidateUpdate(&trait, nil).ToAggregate()).NotTo(HaveOccurred())
Expect(ValidateDelete(&trait).ToAggregate()).NotTo(HaveOccurred())
})
It("Test validate invalid trait", func() {
trait := traitBase
Expect(ValidateCreate(&trait).ToAggregate()).To(HaveOccurred())
Expect(ValidateUpdate(&trait, nil).ToAggregate()).To(HaveOccurred())
Expect(len(ValidateCreate(&trait))).Should(Equal(2))
})
})

View File

@@ -1,115 +0,0 @@
/*
Copyright 2019 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metrics
import (
"context"
"encoding/json"
"net/http"
"k8s.io/klog"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
util "github.com/oam-dev/kubevela/pkg/utils"
)
const (
// SupportedFormat is the only metrics data format we support
SupportedFormat = "prometheus"
// SupportedScheme is the only scheme we support
SupportedScheme = "http"
// DefaultMetricsPath is the default metrics path we support
DefaultMetricsPath = "/metrics"
)
// MutatingHandler handles MetricsTrait
type MutatingHandler struct {
Client client.Client
// Decoder decodes objects
Decoder *admission.Decoder
}
// log is for logging in this package.
var mutatelog = logf.Log.WithName("metricstrait-mutate")
var _ admission.Handler = &MutatingHandler{}
// Handle handles admission requests.
func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
obj := &v1alpha1.MetricsTrait{}
err := h.Decoder.Decode(req, obj)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
DefaultMetrics(obj)
marshalled, err := json.Marshal(obj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
resp := admission.PatchResponseFromRaw(req.AdmissionRequest.Object.Raw, marshalled)
if len(resp.Patches) > 0 {
klog.V(5).Infof("Admit MetricsTrait %s/%s patches: %v", obj.Namespace, obj.Name, util.DumpJSON(resp.Patches))
}
return resp
}
// DefaultMetrics sets all the default value for the metricsTrait
func DefaultMetrics(obj *v1alpha1.MetricsTrait) {
mutatelog.Info("default", "name", obj.Name)
if len(obj.Spec.ScrapeService.Format) == 0 {
mutatelog.Info("default format as prometheus")
obj.Spec.ScrapeService.Format = SupportedFormat
}
if len(obj.Spec.ScrapeService.Path) == 0 {
mutatelog.Info("default path as /metrics")
obj.Spec.ScrapeService.Path = DefaultMetricsPath
}
if len(obj.Spec.ScrapeService.Scheme) == 0 {
mutatelog.Info("default scheme as http")
obj.Spec.ScrapeService.Scheme = SupportedScheme
}
if obj.Spec.ScrapeService.Enabled == nil {
mutatelog.Info("default enabled as true")
obj.Spec.ScrapeService.Enabled = pointer.BoolPtr(true)
}
}
var _ inject.Client = &MutatingHandler{}
// InjectClient injects the client into the MutatingHandler
func (h *MutatingHandler) InjectClient(c client.Client) error {
h.Client = c
return nil
}
var _ admission.DecoderInjector = &MutatingHandler{}
// InjectDecoder injects the decoder into the MutatingHandler
func (h *MutatingHandler) InjectDecoder(d *admission.Decoder) error {
h.Decoder = d
return nil
}

View File

@@ -1,125 +0,0 @@
/*
Copyright 2019 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metrics
import (
"context"
"fmt"
"net/http"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
)
// ValidatingHandler handles MetricsTrait
type ValidatingHandler struct {
Client client.Client
// Decoder decodes objects
Decoder *admission.Decoder
}
// log is for logging in this package.
var validatelog = logf.Log.WithName("metricstrait-validate")
var _ admission.Handler = &ValidatingHandler{}
// Handle handles admission requests.
func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
obj := &v1alpha1.MetricsTrait{}
err := h.Decoder.Decode(req, obj)
if err != nil {
validatelog.Error(err, "decoder failed", "req operation", req.AdmissionRequest.Operation, "req",
req.AdmissionRequest)
return admission.Errored(http.StatusBadRequest, err)
}
switch req.AdmissionRequest.Operation {
case admissionv1beta1.Create:
if allErrs := ValidateCreate(obj); len(allErrs) > 0 {
validatelog.Info("create failed", "name", obj.Name, "err", allErrs.ToAggregate().Error())
return admission.Errored(http.StatusUnprocessableEntity, allErrs.ToAggregate())
}
case admissionv1beta1.Update:
oldObj := &v1alpha1.MetricsTrait{}
if err := h.Decoder.DecodeRaw(req.AdmissionRequest.OldObject, oldObj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if allErrs := ValidateUpdate(obj, oldObj); len(allErrs) > 0 {
validatelog.Info("update failed", "name", obj.Name, "err", allErrs.ToAggregate().Error())
return admission.Errored(http.StatusUnprocessableEntity, allErrs.ToAggregate())
}
default:
// Do nothing for DELETE and CONNECT
}
return admission.ValidationResponse(true, "")
}
// ValidateCreate validates the metricsTrait on creation
func ValidateCreate(r *v1alpha1.MetricsTrait) field.ErrorList {
validatelog.Info("validate create", "name", r.Name)
allErrs := apimachineryvalidation.ValidateObjectMeta(&r.ObjectMeta, true,
apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
fldPath := field.NewPath("spec")
if r.Spec.ScrapeService.Format != SupportedFormat {
allErrs = append(allErrs, field.Invalid(fldPath.Child("ScrapeService.Format"), r.Spec.ScrapeService.Format,
fmt.Sprintf("the data format `%s` is not supported", r.Spec.ScrapeService.Format)))
}
if r.Spec.ScrapeService.Scheme != SupportedScheme {
allErrs = append(allErrs, field.Invalid(fldPath.Child("ScrapeService.Format"), r.Spec.ScrapeService.Scheme,
fmt.Sprintf("the scheme `%s` is not supported", r.Spec.ScrapeService.Scheme)))
}
return allErrs
}
// ValidateUpdate validates the metricsTrait on update
func ValidateUpdate(r *v1alpha1.MetricsTrait, _ *v1alpha1.MetricsTrait) field.ErrorList {
validatelog.Info("validate update", "name", r.Name)
return ValidateCreate(r)
}
// ValidateDelete validates the metricsTrait on delete
func ValidateDelete(r *v1alpha1.MetricsTrait) field.ErrorList {
validatelog.Info("validate delete", "name", r.Name)
return nil
}
var _ inject.Client = &ValidatingHandler{}
// InjectClient injects the client into the ValidatingHandler
func (h *ValidatingHandler) InjectClient(c client.Client) error {
h.Client = c
return nil
}
var _ admission.DecoderInjector = &ValidatingHandler{}
// InjectDecoder injects the decoder into the ValidatingHandler
func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
h.Decoder = d
return nil
}

View File

@@ -376,7 +376,7 @@ var _ = Describe("Versioning mechanism of components", func() {
w2.SetKind("Bar")
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: revisionNameV2}, &w2)
},
time.Second*60, time.Millisecond*500).Should(BeNil())
time.Second*100, time.Millisecond*500).Should(BeNil())
k2, _, _ := unstructured.NestedString(w2.Object, "spec", "key")
Expect(k2).Should(BeEquivalentTo("v2"), fmt.Sprintf("%v", w2.Object))

View File

@@ -309,7 +309,7 @@ var _ = Describe("HealthScope", func() {
healthScope.Status.ScopeHealthCondition)
return healthScope.Status.ScopeHealthCondition
},
time.Second*120, time.Second*5).Should(Equal(v1alpha2.ScopeHealthCondition{
time.Second*150, time.Second*5).Should(Equal(v1alpha2.ScopeHealthCondition{
HealthStatus: v1alpha2.StatusHealthy,
Total: int64(2),
HealthyWorkloads: int64(2),