mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
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:
@@ -12,7 +12,7 @@
|
||||
//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:trivialVersions=true output:artifacts:config=../legacy/charts/vela-core-legacy/crds
|
||||
//go:generate go run ../legacy/convert/main.go ../legacy/charts/vela-core-legacy/crds
|
||||
|
||||
//go:generate go run ../hack/crd/update.go ../charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml ../legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml
|
||||
//go:generate go run ../hack/crd/update.go ../charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml
|
||||
|
||||
package apis
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// Protocol defines network protocols supported for things like container ports.
|
||||
type Protocol string
|
||||
|
||||
// TriggerType defines the type of trigger
|
||||
type TriggerType string
|
||||
|
||||
// Autoscaler is the Schema for the autoscalers API
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
type Autoscaler struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec AutoscalerSpec `json:"spec"`
|
||||
Status AutoscalerStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// SetConditions set condition for CR status
|
||||
func (as *Autoscaler) SetConditions(c ...v1alpha1.Condition) {
|
||||
as.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
// GetCondition get condition from CR status
|
||||
func (as *Autoscaler) GetCondition(conditionType v1alpha1.ConditionType) v1alpha1.Condition {
|
||||
return as.Status.GetCondition(conditionType)
|
||||
}
|
||||
|
||||
// GetWorkloadReference get workload reference
|
||||
func (as *Autoscaler) GetWorkloadReference() v1alpha1.TypedReference {
|
||||
return as.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference set workload reference
|
||||
func (as *Autoscaler) SetWorkloadReference(reference v1alpha1.TypedReference) {
|
||||
as.Spec.WorkloadReference = reference
|
||||
}
|
||||
|
||||
// Trigger defines the trigger of Autoscaler
|
||||
type Trigger struct {
|
||||
// Name is the trigger name, if not set, it will be automatically generated and make it globally unique
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
|
||||
Type TriggerType `json:"type"`
|
||||
|
||||
// Condition set the condition when to trigger scaling
|
||||
Condition map[string]string `json:"condition"`
|
||||
}
|
||||
|
||||
// AutoscalerSpec defines the desired state of Autoscaler
|
||||
type AutoscalerSpec struct {
|
||||
// MinReplicas is the minimal replicas
|
||||
// +optional
|
||||
MinReplicas *int32 `json:"minReplicas,omitempty"`
|
||||
|
||||
// MinReplicas is the maximal replicas
|
||||
// +optional
|
||||
MaxReplicas *int32 `json:"maxReplicas,omitempty"`
|
||||
|
||||
// Triggers lists all triggers
|
||||
Triggers []Trigger `json:"triggers"`
|
||||
|
||||
// TargetWorkload specify the workload which is going to be scaled,
|
||||
// it could be WorkloadReference or the child resource of it
|
||||
TargetWorkload TargetWorkload `json:"targetWorkload,omitempty"`
|
||||
|
||||
// WorkloadReference marks the owner of the workload
|
||||
WorkloadReference v1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
}
|
||||
|
||||
// TargetWorkload holds the a reference to the scale target Object
|
||||
type TargetWorkload struct {
|
||||
Name string `json:"name"`
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
}
|
||||
|
||||
// AutoscalerStatus defines the observed state of Autoscaler
|
||||
type AutoscalerStatus struct {
|
||||
v1alpha1.ConditionedStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// AutoscalerList contains a list of Autoscaler
|
||||
type AutoscalerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Autoscaler `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Autoscaler{}, &AutoscalerList{})
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// MetricsTraitSpec defines the desired state of MetricsTrait
|
||||
type MetricsTraitSpec struct {
|
||||
// An endpoint to be monitored by a ServiceMonitor.
|
||||
ScrapeService ScapeServiceEndPoint `json:"scrapeService"`
|
||||
// WorkloadReference to the workload whose metrics needs to be exposed
|
||||
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
}
|
||||
|
||||
// ScapeServiceEndPoint defines a scrapeable endpoint serving Prometheus metrics.
|
||||
type ScapeServiceEndPoint struct {
|
||||
// The format of the metrics data,
|
||||
// The default and only supported format is "prometheus" for now
|
||||
Format string `json:"format,omitempty"`
|
||||
// Number or name of the port to access on the pods targeted by the service.
|
||||
// The default is discovered automatically from podTemplate, metricTrait will create a service for the workload
|
||||
TargetPort intstr.IntOrString `json:"port,omitempty"`
|
||||
// Route service traffic to pods with label keys and values matching this
|
||||
// The default is discovered automatically from podTemplate.
|
||||
// If no podTemplate, use the labels specified here, or use the labels of the workload
|
||||
TargetSelector map[string]string `json:"selector,omitempty"`
|
||||
// HTTP path to scrape for metrics.
|
||||
// default is /metrics
|
||||
// +optional
|
||||
Path string `json:"path,omitempty"`
|
||||
// Scheme at which metrics should be scraped
|
||||
// The default and only supported scheme is "http"
|
||||
// +optional
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
// The default is true
|
||||
// +optional
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
// MetricsTraitStatus defines the observed state of MetricsTrait
|
||||
type MetricsTraitStatus struct {
|
||||
runtimev1alpha1.ConditionedStatus `json:",inline"`
|
||||
|
||||
// ServiceMonitorName managed by this trait
|
||||
ServiceMonitorName string `json:"serviceMonitorName,omitempty"`
|
||||
|
||||
// Port is the real port monitoring
|
||||
Port intstr.IntOrString `json:"port,omitempty"`
|
||||
// SelectorLabels is the real labels selected
|
||||
SelectorLabels map[string]string `json:"selectorLabels,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// MetricsTrait is the Schema for the metricstraits API
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
// +kubebuilder:subresource:status
|
||||
type MetricsTrait struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec MetricsTraitSpec `json:"spec"`
|
||||
Status MetricsTraitStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// MetricsTraitList contains a list of MetricsTrait
|
||||
type MetricsTraitList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []MetricsTrait `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&MetricsTrait{}, &MetricsTraitList{})
|
||||
}
|
||||
|
||||
var _ oam.Trait = &MetricsTrait{}
|
||||
|
||||
// SetConditions for set CR condition
|
||||
func (tr *MetricsTrait) SetConditions(c ...runtimev1alpha1.Condition) {
|
||||
tr.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
// GetCondition for get CR condition
|
||||
func (tr *MetricsTrait) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
|
||||
return tr.Status.GetCondition(c)
|
||||
}
|
||||
|
||||
// GetWorkloadReference of this MetricsTrait.
|
||||
func (tr *MetricsTrait) GetWorkloadReference() runtimev1alpha1.TypedReference {
|
||||
return tr.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference of this MetricsTrait.
|
||||
func (tr *MetricsTrait) SetWorkloadReference(r runtimev1alpha1.TypedReference) {
|
||||
tr.Spec.WorkloadReference = r
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// RouteSpec defines the desired state of Route
|
||||
type RouteSpec struct {
|
||||
// WorkloadReference to the workload whose metrics needs to be exposed
|
||||
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
|
||||
// Host is the host of the route
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS indicate route trait will create SSL secret using cert-manager with specified issuer
|
||||
// If this is nil, route trait will use a selfsigned issuer
|
||||
TLS *TLS `json:"tls,omitempty"`
|
||||
|
||||
// Rules contain multiple rules of route
|
||||
Rules []Rule `json:"rules,omitempty"`
|
||||
|
||||
// Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
Provider string `json:"provider,omitempty"`
|
||||
|
||||
// IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
IngressClass string `json:"ingressClass,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines to route rule
|
||||
type Rule struct {
|
||||
// Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Path is location Path, default for "/"
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
// RewriteTarget will rewrite request from Path to RewriteTarget path.
|
||||
RewriteTarget string `json:"rewriteTarget,omitempty"`
|
||||
|
||||
// CustomHeaders pass a custom list of headers to the backend service.
|
||||
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
|
||||
|
||||
// DefaultBackend will become the ingress default backend if the backend is not available
|
||||
DefaultBackend *runtimev1alpha1.TypedReference `json:"defaultBackend,omitempty"`
|
||||
|
||||
// Backend indicate how to connect backend service
|
||||
// If it's nil, will auto discovery
|
||||
Backend *Backend `json:"backend,omitempty"`
|
||||
}
|
||||
|
||||
// TLS defines certificate issuer and type for mTLS configuration
|
||||
type TLS struct {
|
||||
IssuerName string `json:"issuerName,omitempty"`
|
||||
|
||||
// Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
// +kubebuilder:default:=Issuer
|
||||
Type IssuerType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// IssuerType defines the type of issuer
|
||||
type IssuerType string
|
||||
|
||||
const (
|
||||
// ClusterIssuer is a cluster level type of issuer
|
||||
ClusterIssuer IssuerType = "ClusterIssuer"
|
||||
// NamespaceIssuer is the default one
|
||||
NamespaceIssuer IssuerType = "Issuer"
|
||||
)
|
||||
|
||||
// Backend defines backend configure for route trait.
|
||||
// Route will automatically discover podSpec and label for BackendService.
|
||||
// If BackendService is already set, discovery won't work.
|
||||
// If BackendService is not set, the discovery mechanism will work.
|
||||
type Backend struct {
|
||||
// ReadTimeout used for setting read timeout duration for backend service, the unit is second.
|
||||
ReadTimeout int `json:"readTimeout,omitempty"`
|
||||
// SendTimeout used for setting send timeout duration for backend service, the unit is second.
|
||||
SendTimeout int `json:"sendTimeout,omitempty"`
|
||||
// BackendService specifies the backend K8s service and port, it's optional
|
||||
BackendService *BackendServiceRef `json:"backendService,omitempty"`
|
||||
}
|
||||
|
||||
// BackendServiceRef specifies the backend K8s service and port, if specified, the two fields are all required
|
||||
type BackendServiceRef struct {
|
||||
// Port allow you direct specify backend service port.
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// ServiceName allow you direct specify K8s service for backend service.
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
|
||||
// RouteStatus defines the observed state of Route
|
||||
type RouteStatus struct {
|
||||
Ingresses []runtimev1alpha1.TypedReference `json:"ingresses,omitempty"`
|
||||
Service *runtimev1alpha1.TypedReference `json:"service,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
runtimev1alpha1.ConditionedStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// Route is the Schema for the routes API
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
// +kubebuilder:subresource:status
|
||||
type Route struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec RouteSpec `json:"spec,omitempty"`
|
||||
Status RouteStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// RouteList contains a list of Route
|
||||
// +kubebuilder:object:root=true
|
||||
type RouteList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Route `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Route{}, &RouteList{})
|
||||
}
|
||||
|
||||
var _ oam.Trait = &Route{}
|
||||
|
||||
// SetConditions set condition for CR status
|
||||
func (r *Route) SetConditions(c ...runtimev1alpha1.Condition) {
|
||||
r.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
// GetCondition get condition from CR status
|
||||
func (r *Route) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
|
||||
return r.Status.GetCondition(c)
|
||||
}
|
||||
|
||||
// GetWorkloadReference of this Route Trait.
|
||||
func (r *Route) GetWorkloadReference() runtimev1alpha1.TypedReference {
|
||||
return r.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference of this Route Trait.
|
||||
func (r *Route) SetWorkloadReference(rt runtimev1alpha1.TypedReference) {
|
||||
r.Spec.WorkloadReference = rt
|
||||
}
|
||||
@@ -26,151 +26,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Autoscaler) DeepCopyInto(out *Autoscaler) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Autoscaler.
|
||||
func (in *Autoscaler) DeepCopy() *Autoscaler {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Autoscaler)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Autoscaler) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AutoscalerList) DeepCopyInto(out *AutoscalerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Autoscaler, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerList.
|
||||
func (in *AutoscalerList) DeepCopy() *AutoscalerList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AutoscalerList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *AutoscalerList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AutoscalerSpec) DeepCopyInto(out *AutoscalerSpec) {
|
||||
*out = *in
|
||||
if in.MinReplicas != nil {
|
||||
in, out := &in.MinReplicas, &out.MinReplicas
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.MaxReplicas != nil {
|
||||
in, out := &in.MaxReplicas, &out.MaxReplicas
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.Triggers != nil {
|
||||
in, out := &in.Triggers, &out.Triggers
|
||||
*out = make([]Trigger, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
out.TargetWorkload = in.TargetWorkload
|
||||
out.WorkloadReference = in.WorkloadReference
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerSpec.
|
||||
func (in *AutoscalerSpec) DeepCopy() *AutoscalerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AutoscalerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AutoscalerStatus) DeepCopyInto(out *AutoscalerStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerStatus.
|
||||
func (in *AutoscalerStatus) DeepCopy() *AutoscalerStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AutoscalerStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Backend) DeepCopyInto(out *Backend) {
|
||||
*out = *in
|
||||
if in.BackendService != nil {
|
||||
in, out := &in.BackendService, &out.BackendService
|
||||
*out = new(BackendServiceRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
|
||||
func (in *Backend) DeepCopy() *Backend {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Backend)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackendServiceRef) DeepCopyInto(out *BackendServiceRef) {
|
||||
*out = *in
|
||||
out.Port = in.Port
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendServiceRef.
|
||||
func (in *BackendServiceRef) DeepCopy() *BackendServiceRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackendServiceRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CanaryMetric) DeepCopyInto(out *CanaryMetric) {
|
||||
*out = *in
|
||||
@@ -221,106 +76,6 @@ func (in *MetricsExpectedRange) DeepCopy() *MetricsExpectedRange {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTrait) DeepCopyInto(out *MetricsTrait) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTrait.
|
||||
func (in *MetricsTrait) DeepCopy() *MetricsTrait {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTrait)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MetricsTrait) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTraitList) DeepCopyInto(out *MetricsTraitList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]MetricsTrait, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTraitList.
|
||||
func (in *MetricsTraitList) DeepCopy() *MetricsTraitList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTraitList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MetricsTraitList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTraitSpec) DeepCopyInto(out *MetricsTraitSpec) {
|
||||
*out = *in
|
||||
in.ScrapeService.DeepCopyInto(&out.ScrapeService)
|
||||
out.WorkloadReference = in.WorkloadReference
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTraitSpec.
|
||||
func (in *MetricsTraitSpec) DeepCopy() *MetricsTraitSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTraitSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetricsTraitStatus) DeepCopyInto(out *MetricsTraitStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
out.Port = in.Port
|
||||
if in.SelectorLabels != nil {
|
||||
in, out := &in.SelectorLabels, &out.SelectorLabels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsTraitStatus.
|
||||
func (in *MetricsTraitStatus) DeepCopy() *MetricsTraitStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetricsTraitStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSpecWorkload) DeepCopyInto(out *PodSpecWorkload) {
|
||||
*out = *in
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: autoscalers.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Autoscaler
|
||||
listKind: AutoscalerList
|
||||
plural: autoscalers
|
||||
singular: autoscaler
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Autoscaler is the Schema for the autoscalers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: AutoscalerSpec defines the desired state of Autoscaler
|
||||
properties:
|
||||
maxReplicas:
|
||||
description: MinReplicas is the maximal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
minReplicas:
|
||||
description: MinReplicas is the minimal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
targetWorkload:
|
||||
description: TargetWorkload specify the workload which is going to be scaled, it could be WorkloadReference or the child resource of it
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
triggers:
|
||||
description: Triggers lists all triggers
|
||||
items:
|
||||
description: Trigger defines the trigger of Autoscaler
|
||||
properties:
|
||||
condition:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Condition set the condition when to trigger scaling
|
||||
type: object
|
||||
name:
|
||||
description: Name is the trigger name, if not set, it will be automatically generated and make it globally unique
|
||||
type: string
|
||||
type:
|
||||
description: Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
|
||||
type: string
|
||||
required:
|
||||
- condition
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
workloadRef:
|
||||
description: WorkloadReference marks the owner of the workload
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- triggers
|
||||
type: object
|
||||
status:
|
||||
description: AutoscalerStatus defines the observed state of Autoscaler
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,145 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: metricstraits.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: MetricsTrait
|
||||
listKind: MetricsTraitList
|
||||
plural: metricstraits
|
||||
singular: metricstrait
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: MetricsTrait is the Schema for the metricstraits API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: MetricsTraitSpec defines the desired state of MetricsTrait
|
||||
properties:
|
||||
scrapeService:
|
||||
description: An endpoint to be monitored by a ServiceMonitor.
|
||||
properties:
|
||||
enabled:
|
||||
description: The default is true
|
||||
type: boolean
|
||||
format:
|
||||
description: The format of the metrics data, The default and only supported format is "prometheus" for now
|
||||
type: string
|
||||
path:
|
||||
description: HTTP path to scrape for metrics. default is /metrics
|
||||
type: string
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Number or name of the port to access on the pods targeted by the service. The default is discovered automatically from podTemplate, metricTrait will create a service for the workload
|
||||
x-kubernetes-int-or-string: true
|
||||
scheme:
|
||||
description: Scheme at which metrics should be scraped The default and only supported scheme is "http"
|
||||
type: string
|
||||
selector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Route service traffic to pods with label keys and values matching this The default is discovered automatically from podTemplate. If no podTemplate, use the labels specified here, or use the labels of the workload
|
||||
type: object
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- scrapeService
|
||||
type: object
|
||||
status:
|
||||
description: MetricsTraitStatus defines the observed state of MetricsTrait
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port is the real port monitoring
|
||||
x-kubernetes-int-or-string: true
|
||||
selectorLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: SelectorLabels is the real labels selected
|
||||
type: object
|
||||
serviceMonitorName:
|
||||
description: ServiceMonitorName managed by this trait
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,232 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: routes.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Route
|
||||
listKind: RouteList
|
||||
plural: routes
|
||||
singular: route
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Route is the Schema for the routes API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RouteSpec defines the desired state of Route
|
||||
properties:
|
||||
host:
|
||||
description: Host is the host of the route
|
||||
type: string
|
||||
ingressClass:
|
||||
description: IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
type: string
|
||||
provider:
|
||||
description: Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
type: string
|
||||
rules:
|
||||
description: Rules contain multiple rules of route
|
||||
items:
|
||||
description: Rule defines to route rule
|
||||
properties:
|
||||
backend:
|
||||
description: Backend indicate how to connect backend service If it's nil, will auto discovery
|
||||
properties:
|
||||
backendService:
|
||||
description: BackendService specifies the backend K8s service and port, it's optional
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port allow you direct specify backend service port.
|
||||
x-kubernetes-int-or-string: true
|
||||
serviceName:
|
||||
description: ServiceName allow you direct specify K8s service for backend service.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- serviceName
|
||||
type: object
|
||||
readTimeout:
|
||||
description: ReadTimeout used for setting read timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
sendTimeout:
|
||||
description: SendTimeout used for setting send timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
type: object
|
||||
customHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: CustomHeaders pass a custom list of headers to the backend service.
|
||||
type: object
|
||||
defaultBackend:
|
||||
description: DefaultBackend will become the ingress default backend if the backend is not available
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
|
||||
type: string
|
||||
path:
|
||||
description: Path is location Path, default for "/"
|
||||
type: string
|
||||
rewriteTarget:
|
||||
description: RewriteTarget will rewrite request from Path to RewriteTarget path.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
tls:
|
||||
description: TLS indicate route trait will create SSL secret using cert-manager with specified issuer If this is nil, route trait will use a selfsigned issuer
|
||||
properties:
|
||||
issuerName:
|
||||
type: string
|
||||
type:
|
||||
default: Issuer
|
||||
description: Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
type: string
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: RouteStatus defines the observed state of Route
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
ingresses:
|
||||
items:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
service:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
1
go.mod
@@ -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
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: autoscalers.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Autoscaler
|
||||
listKind: AutoscalerList
|
||||
plural: autoscalers
|
||||
singular: autoscaler
|
||||
scope: Namespaced
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Autoscaler is the Schema for the autoscalers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: AutoscalerSpec defines the desired state of Autoscaler
|
||||
properties:
|
||||
maxReplicas:
|
||||
description: MinReplicas is the maximal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
minReplicas:
|
||||
description: MinReplicas is the minimal replicas
|
||||
format: int32
|
||||
type: integer
|
||||
targetWorkload:
|
||||
description: TargetWorkload specify the workload which is going to be scaled, it could be WorkloadReference or the child resource of it
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
triggers:
|
||||
description: Triggers lists all triggers
|
||||
items:
|
||||
description: Trigger defines the trigger of Autoscaler
|
||||
properties:
|
||||
condition:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Condition set the condition when to trigger scaling
|
||||
type: object
|
||||
name:
|
||||
description: Name is the trigger name, if not set, it will be automatically generated and make it globally unique
|
||||
type: string
|
||||
type:
|
||||
description: Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
|
||||
type: string
|
||||
required:
|
||||
- condition
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
workloadRef:
|
||||
description: WorkloadReference marks the owner of the workload
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- triggers
|
||||
type: object
|
||||
status:
|
||||
description: AutoscalerStatus defines the observed state of Autoscaler
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,146 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: metricstraits.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: MetricsTrait
|
||||
listKind: MetricsTraitList
|
||||
plural: metricstraits
|
||||
singular: metricstrait
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: MetricsTrait is the Schema for the metricstraits API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: MetricsTraitSpec defines the desired state of MetricsTrait
|
||||
properties:
|
||||
scrapeService:
|
||||
description: An endpoint to be monitored by a ServiceMonitor.
|
||||
properties:
|
||||
enabled:
|
||||
description: The default is true
|
||||
type: boolean
|
||||
format:
|
||||
description: The format of the metrics data, The default and only supported format is "prometheus" for now
|
||||
type: string
|
||||
path:
|
||||
description: HTTP path to scrape for metrics. default is /metrics
|
||||
type: string
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Number or name of the port to access on the pods targeted by the service. The default is discovered automatically from podTemplate, metricTrait will create a service for the workload
|
||||
x-kubernetes-int-or-string: true
|
||||
scheme:
|
||||
description: Scheme at which metrics should be scraped The default and only supported scheme is "http"
|
||||
type: string
|
||||
selector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Route service traffic to pods with label keys and values matching this The default is discovered automatically from podTemplate. If no podTemplate, use the labels specified here, or use the labels of the workload
|
||||
type: object
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- scrapeService
|
||||
type: object
|
||||
status:
|
||||
description: MetricsTraitStatus defines the observed state of MetricsTrait
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port is the real port monitoring
|
||||
x-kubernetes-int-or-string: true
|
||||
selectorLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: SelectorLabels is the real labels selected
|
||||
type: object
|
||||
serviceMonitorName:
|
||||
description: ServiceMonitorName managed by this trait
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,232 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: routes.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
categories:
|
||||
- oam
|
||||
kind: Route
|
||||
listKind: RouteList
|
||||
plural: routes
|
||||
singular: route
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Route is the Schema for the routes API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RouteSpec defines the desired state of Route
|
||||
properties:
|
||||
host:
|
||||
description: Host is the host of the route
|
||||
type: string
|
||||
ingressClass:
|
||||
description: IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
type: string
|
||||
provider:
|
||||
description: Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
type: string
|
||||
rules:
|
||||
description: Rules contain multiple rules of route
|
||||
items:
|
||||
description: Rule defines to route rule
|
||||
properties:
|
||||
backend:
|
||||
description: Backend indicate how to connect backend service If it's nil, will auto discovery
|
||||
properties:
|
||||
backendService:
|
||||
description: BackendService specifies the backend K8s service and port, it's optional
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Port allow you direct specify backend service port.
|
||||
x-kubernetes-int-or-string: true
|
||||
serviceName:
|
||||
description: ServiceName allow you direct specify K8s service for backend service.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- serviceName
|
||||
type: object
|
||||
readTimeout:
|
||||
description: ReadTimeout used for setting read timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
sendTimeout:
|
||||
description: SendTimeout used for setting send timeout duration for backend service, the unit is second.
|
||||
type: integer
|
||||
type: object
|
||||
customHeaders:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: CustomHeaders pass a custom list of headers to the backend service.
|
||||
type: object
|
||||
defaultBackend:
|
||||
description: DefaultBackend will become the ingress default backend if the backend is not available
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
|
||||
type: string
|
||||
path:
|
||||
description: Path is location Path, default for "/"
|
||||
type: string
|
||||
rewriteTarget:
|
||||
description: RewriteTarget will rewrite request from Path to RewriteTarget path.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
tls:
|
||||
description: TLS indicate route trait will create SSL secret using cert-manager with specified issuer If this is nil, route trait will use a selfsigned issuer
|
||||
properties:
|
||||
issuerName:
|
||||
type: string
|
||||
type:
|
||||
description: Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
type: string
|
||||
type: object
|
||||
workloadRef:
|
||||
description: WorkloadReference to the workload whose metrics needs to be exposed
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: RouteStatus defines the observed state of Route
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions of the resource.
|
||||
items:
|
||||
description: A Condition that may apply to a resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time this condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A Message containing details about this condition's last transition from one status to another, if any.
|
||||
type: string
|
||||
reason:
|
||||
description: A Reason for this condition's last transition from one status to another.
|
||||
type: string
|
||||
status:
|
||||
description: Status of this condition; is it currently True, False, or Unknown?
|
||||
type: string
|
||||
type:
|
||||
description: Type of this condition. At most one of each condition type may apply to a resource at any point in time.
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
ingresses:
|
||||
items:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
service:
|
||||
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference cluster-scoped objects or objects where the namespace is already known.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -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",
|
||||
|
||||
@@ -20,10 +20,7 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/autoscaler"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/routes"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
)
|
||||
|
||||
@@ -33,23 +30,14 @@ func Setup(mgr ctrl.Manager, disableCaps string) error {
|
||||
switch disableCaps {
|
||||
case common.DisableNoneCaps:
|
||||
functions = []func(ctrl.Manager) error{
|
||||
metrics.Setup, podspecworkload.Setup, routes.Setup, autoscaler.Setup,
|
||||
podspecworkload.Setup,
|
||||
}
|
||||
case common.DisableAllCaps:
|
||||
default:
|
||||
disableCapsSet := utils.StoreInSet(disableCaps)
|
||||
if !disableCapsSet.Contains(common.MetricsControllerName) {
|
||||
functions = append(functions, metrics.Setup)
|
||||
}
|
||||
if !disableCapsSet.Contains(common.PodspecWorkloadControllerName) {
|
||||
functions = append(functions, podspecworkload.Setup)
|
||||
}
|
||||
if !disableCapsSet.Contains(common.RouteControllerName) {
|
||||
functions = append(functions, routes.Setup)
|
||||
}
|
||||
if !disableCapsSet.Contains(common.AutoscaleControllerName) {
|
||||
functions = append(functions, autoscaler.Setup)
|
||||
}
|
||||
}
|
||||
|
||||
for _, setup := range functions {
|
||||
|
||||
@@ -1,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)
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
package autoscaler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
kedav1alpha1 "github.com/wonderflow/keda-api/api/v1alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
func (r *Reconciler) scaleByKEDA(scaler v1alpha1.Autoscaler, namespace string, log logr.Logger) error {
|
||||
ctx := context.Background()
|
||||
minReplicas := scaler.Spec.MinReplicas
|
||||
maxReplicas := scaler.Spec.MaxReplicas
|
||||
triggers := scaler.Spec.Triggers
|
||||
scalerName := scaler.Name
|
||||
targetWorkload := scaler.Spec.TargetWorkload
|
||||
|
||||
var kedaTriggers []kedav1alpha1.ScaleTriggers
|
||||
var err error
|
||||
for _, t := range triggers {
|
||||
if t.Type == CronType {
|
||||
cronKedaTriggers, reason, err := r.prepareKEDACronScalerTriggerSpec(scaler, t)
|
||||
if err != nil {
|
||||
log.Error(err, reason)
|
||||
r.record.Event(&scaler, event.Warning(event.Reason(reason), err))
|
||||
return err
|
||||
}
|
||||
kedaTriggers = append(kedaTriggers, cronKedaTriggers...)
|
||||
} else {
|
||||
kedaTriggers = append(kedaTriggers, kedav1alpha1.ScaleTriggers{
|
||||
Type: string(t.Type),
|
||||
Name: t.Name,
|
||||
Metadata: t.Condition,
|
||||
|
||||
// TODO(wonderflow): add auth in the future
|
||||
AuthenticationRef: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
spec := kedav1alpha1.ScaledObjectSpec{
|
||||
ScaleTargetRef: &kedav1alpha1.ScaleTarget{
|
||||
APIVersion: targetWorkload.APIVersion,
|
||||
Kind: targetWorkload.Kind,
|
||||
Name: targetWorkload.Name,
|
||||
},
|
||||
MinReplicaCount: minReplicas,
|
||||
MaxReplicaCount: maxReplicas,
|
||||
Triggers: kedaTriggers,
|
||||
}
|
||||
var scaleObj kedav1alpha1.ScaledObject
|
||||
err = r.Client.Get(ctx, types.NamespacedName{Name: scalerName, Namespace: namespace}, &scaleObj)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
scaleObj := kedav1alpha1.ScaledObject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: scalerName,
|
||||
Namespace: namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: scaler.APIVersion,
|
||||
Kind: scaler.Kind,
|
||||
UID: scaler.GetUID(),
|
||||
Name: scalerName,
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
if err := r.Client.Create(ctx, &scaleObj); err != nil {
|
||||
log.Error(err, "failed to create KEDA ScaledObj", "ScaledObject", scaleObj)
|
||||
return err
|
||||
}
|
||||
log.Info("KEDA ScaledObj created", "ScaledObjectName", scalerName)
|
||||
}
|
||||
} else {
|
||||
scaleObj.Spec = spec
|
||||
if err := r.Client.Update(ctx, &scaleObj); err != nil {
|
||||
log.Error(err, "failed to update KEDA ScaledObj", "ScaledObject", scaleObj)
|
||||
return err
|
||||
}
|
||||
log.Info("KEDA ScaledObj updated", "ScaledObjectName", scalerName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CronTypeCondition defines the cron type for autoscaler
|
||||
type CronTypeCondition struct {
|
||||
// StartAt is the time when the scaler starts, in format `"HHMM"` for example, "08:00"
|
||||
StartAt string `json:"startAt,omitempty"`
|
||||
|
||||
// Duration means how long the target scaling will keep, after the time of duration, the scaling will stop
|
||||
Duration string `json:"duration,omitempty"`
|
||||
|
||||
// Days means in which days the condition will take effect
|
||||
Days string `json:"days,omitempty"`
|
||||
|
||||
// Replicas is the expected replicas
|
||||
Replicas string `json:"replicas,omitempty"`
|
||||
|
||||
// Timezone defines the time zone, default to the timezone of the Kubernetes cluster
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
// GetCronTypeCondition will get condition from map
|
||||
func GetCronTypeCondition(condition map[string]string) (*CronTypeCondition, error) {
|
||||
data, err := json.Marshal(condition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cronCon CronTypeCondition
|
||||
if err = json.Unmarshal(data, &cronCon); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cronCon, nil
|
||||
}
|
||||
|
||||
// prepareKEDACronScalerTriggerSpec converts Autoscaler spec into KEDA Cron scaler spec
|
||||
func (r *Reconciler) prepareKEDACronScalerTriggerSpec(scaler v1alpha1.Autoscaler, t v1alpha1.Trigger) ([]kedav1alpha1.ScaleTriggers, string, error) {
|
||||
var kedaTriggers []kedav1alpha1.ScaleTriggers
|
||||
targetWorkload := scaler.Spec.TargetWorkload
|
||||
if targetWorkload.Name == "" {
|
||||
err := errors.New(SpecWarningTargetWorkloadNotSet)
|
||||
return kedaTriggers, SpecWarningTargetWorkloadNotSet, err
|
||||
}
|
||||
triggerCondition, err := GetCronTypeCondition(t.Condition)
|
||||
if err != nil {
|
||||
return nil, "convert cron condition failed", err
|
||||
}
|
||||
startAt := triggerCondition.StartAt
|
||||
if startAt == "" {
|
||||
return kedaTriggers, SpecWarningStartAtTimeRequired, errors.New(SpecWarningStartAtTimeRequired)
|
||||
}
|
||||
duration := triggerCondition.Duration
|
||||
if duration == "" {
|
||||
return kedaTriggers, SpecWarningDurationTimeRequired, errors.New(SpecWarningDurationTimeRequired)
|
||||
}
|
||||
startTime, err := time.Parse("15:04", startAt)
|
||||
if err != nil {
|
||||
return kedaTriggers, SpecWarningStartAtTimeFormat, err
|
||||
}
|
||||
var startHour, startMinute int
|
||||
startHour = startTime.Hour()
|
||||
startMinute = startTime.Minute()
|
||||
|
||||
durationTime, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
return kedaTriggers, SpecWarningDurationTimeNotInRightFormat, err
|
||||
}
|
||||
durationHour := durationTime.Hours()
|
||||
durationMin := int(durationTime.Minutes()) % 60
|
||||
endMinite := startMinute + durationMin
|
||||
endHour := int(durationHour) + startHour
|
||||
|
||||
if endMinite >= 60 {
|
||||
endMinite %= 60
|
||||
endHour++
|
||||
}
|
||||
var durationOneMoreDay int
|
||||
if endHour >= 24 {
|
||||
endHour %= 24
|
||||
durationOneMoreDay = 1
|
||||
}
|
||||
replicas, err := strconv.Atoi(triggerCondition.Replicas)
|
||||
if err != nil {
|
||||
return nil, "parse replica failed", err
|
||||
}
|
||||
if replicas == 0 {
|
||||
return kedaTriggers, SpecWarningReplicasRequired, errors.New(SpecWarningReplicasRequired)
|
||||
}
|
||||
|
||||
timezone := triggerCondition.Timezone
|
||||
|
||||
days := strings.Split(triggerCondition.Days, ",")
|
||||
var dayNo []int
|
||||
|
||||
for i, d := range days {
|
||||
d = strings.TrimSpace(d)
|
||||
days[i] = d
|
||||
var found = false
|
||||
for i := 0; i < 7; i++ {
|
||||
if strings.EqualFold(time.Weekday(i).String(), d) {
|
||||
dayNo = append(dayNo, i)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, "", fmt.Errorf("wrong format %s, should be one of %v", d,
|
||||
[]string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"})
|
||||
}
|
||||
}
|
||||
|
||||
for idx, n := range dayNo {
|
||||
kedaTrigger := kedav1alpha1.ScaleTriggers{
|
||||
Type: string(t.Type),
|
||||
Name: t.Name + "-" + days[idx],
|
||||
Metadata: map[string]string{
|
||||
"timezone": timezone,
|
||||
"start": fmt.Sprintf("%d %d * * %d", startMinute, startHour, n),
|
||||
"end": fmt.Sprintf("%d %d * * %d", endMinite, endHour, (n+durationOneMoreDay)%7),
|
||||
"desiredReplicas": strconv.Itoa(replicas),
|
||||
},
|
||||
}
|
||||
kedaTriggers = append(kedaTriggers, kedaTrigger)
|
||||
}
|
||||
return kedaTriggers, "", nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package autoscaler
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = standardv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
package autoscaler
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// constants used in autoscaler controller
|
||||
const (
|
||||
CronType v1alpha1.TriggerType = "cron"
|
||||
CPUType v1alpha1.TriggerType = "cpu"
|
||||
)
|
||||
@@ -1,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)
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
metricsTraitKind = reflect.TypeOf(v1alpha1.MetricsTrait{}).Name()
|
||||
metricsTraitAPIVersion = v1alpha1.SchemeGroupVersion.String()
|
||||
deploymentKind = reflect.TypeOf(appsv1.Deployment{}).Name()
|
||||
deploymentAPIVersion = appsv1.SchemeGroupVersion.String()
|
||||
)
|
||||
|
||||
var _ = Describe("Metrics Trait Integration Test", func() {
|
||||
// common var init
|
||||
ctx := context.Background()
|
||||
namespaceName := "metricstrait-integration-test"
|
||||
traitLabel := map[string]string{"trait": "metricsTraitBase"}
|
||||
deployLabel := map[string]string{"standard.oam.dev": "oam-test-deployment"}
|
||||
podPort := 8080
|
||||
targetPort := intstr.FromInt(podPort)
|
||||
metricsPath := "/notMetrics"
|
||||
scheme := "http"
|
||||
var ns corev1.Namespace
|
||||
var metricsTraitBase v1alpha1.MetricsTrait
|
||||
var workloadBase appsv1.Deployment
|
||||
|
||||
BeforeEach(func() {
|
||||
logf.Log.Info("[TEST] Set up resources before an integration test")
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespaceName,
|
||||
},
|
||||
}
|
||||
By("Create the Namespace for test")
|
||||
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
|
||||
metricsTraitBase = v1alpha1.MetricsTrait{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: metricsTraitKind,
|
||||
APIVersion: metricsTraitAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns.Name,
|
||||
Labels: traitLabel,
|
||||
},
|
||||
Spec: v1alpha1.MetricsTraitSpec{
|
||||
ScrapeService: v1alpha1.ScapeServiceEndPoint{
|
||||
TargetPort: targetPort,
|
||||
Path: metricsPath,
|
||||
Scheme: scheme,
|
||||
Enabled: pointer.BoolPtr(true),
|
||||
},
|
||||
WorkloadReference: runtimev1alpha1.TypedReference{
|
||||
APIVersion: deploymentAPIVersion,
|
||||
Kind: deploymentKind,
|
||||
},
|
||||
},
|
||||
}
|
||||
workloadBase = appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns.Name,
|
||||
Labels: deployLabel,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: deployLabel,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: deployLabel,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container-name",
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
Command: []string{"containerCommand"},
|
||||
Args: []string{"containerArguments"},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(podPort),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Control-runtime test environment has a bug that can't delete resources like deployment/namespaces
|
||||
// We have to use different names to segregate between tests
|
||||
logf.Log.Info("[TEST] Clean up resources after an integration test")
|
||||
})
|
||||
|
||||
It("Test with deployment as workloadBase without selector", func() {
|
||||
testName := "deploy-without-selector"
|
||||
By("Create the deployment as the workloadBase")
|
||||
workload := workloadBase
|
||||
workload.Name = testName + "-workload"
|
||||
Expect(k8sClient.Create(ctx, &workload)).ToNot(HaveOccurred())
|
||||
|
||||
By("Create the metrics trait pointing to the workloadBase")
|
||||
metricsTrait := metricsTraitBase
|
||||
metricsTrait.Name = testName + "-trait"
|
||||
metricsTrait.Spec.WorkloadReference.Name = workload.Name
|
||||
Expect(k8sClient.Create(ctx, &metricsTrait)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdService := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: "oam-" + workload.GetName()},
|
||||
&createdService)
|
||||
},
|
||||
time.Second*10, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdService.Spec.Ports)
|
||||
Expect(createdService.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(createdService.Labels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(len(createdService.Spec.Ports)).Should(Equal(1))
|
||||
Expect(createdService.Spec.Ports[0].Port).Should(BeEquivalentTo(servicePort))
|
||||
Expect(createdService.Spec.Selector).Should(Equal(deployLabel))
|
||||
By("Check that we have created the serviceMonitor in the pre-defined namespaceName")
|
||||
var serviceMonitor monitoringv1.ServiceMonitor
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ServiceMonitorNSName, Name: metricsTrait.GetName()},
|
||||
&serviceMonitor)
|
||||
},
|
||||
time.Second*5, time.Millisecond*50).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created serviceMonitor", "service end ports", serviceMonitor.Spec.Endpoints)
|
||||
Expect(serviceMonitor.GetNamespace()).Should(Equal(ServiceMonitorNSName))
|
||||
Expect(serviceMonitor.Spec.Selector.MatchLabels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(serviceMonitor.Spec.Selector.MatchExpressions).Should(BeNil())
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.MatchNames).Should(Equal([]string{metricsTrait.Namespace}))
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.Any).Should(BeFalse())
|
||||
Expect(len(serviceMonitor.Spec.Endpoints)).Should(Equal(1))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Port).Should(BeEmpty())
|
||||
Expect(*serviceMonitor.Spec.Endpoints[0].TargetPort).Should(BeEquivalentTo(targetPort))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Scheme).Should(Equal(scheme))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Path).Should(Equal(metricsPath))
|
||||
})
|
||||
|
||||
It("Test with deployment as workloadBase selector", func() {
|
||||
testName := "deploy-with-selector"
|
||||
By("Create the deployment as the workloadBase")
|
||||
workload := workloadBase.DeepCopy()
|
||||
workload.Name = testName + "-workload"
|
||||
Expect(k8sClient.Create(ctx, workload)).ToNot(HaveOccurred())
|
||||
|
||||
By("Create the metrics trait pointing to the workloadBase")
|
||||
podSelector := map[string]string{"podlabel": "goodboy"}
|
||||
metricsTrait := metricsTraitBase
|
||||
metricsTrait.Name = testName + "-trait"
|
||||
metricsTrait.Spec.WorkloadReference.Name = workload.Name
|
||||
metricsTrait.Spec.ScrapeService.TargetSelector = podSelector
|
||||
Expect(k8sClient.Create(ctx, &metricsTrait)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdService := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: "oam-" + workload.GetName()},
|
||||
&createdService)
|
||||
},
|
||||
time.Second*10, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdService.Spec.Ports)
|
||||
Expect(createdService.Labels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(createdService.Spec.Selector).Should(Equal(deployLabel))
|
||||
By("Check that we have created the serviceMonitor in the pre-defined namespaceName")
|
||||
var serviceMonitor monitoringv1.ServiceMonitor
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ServiceMonitorNSName, Name: metricsTrait.GetName()},
|
||||
&serviceMonitor)
|
||||
},
|
||||
time.Second*5, time.Millisecond*50).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created serviceMonitor", "service end ports", serviceMonitor.Spec.Endpoints)
|
||||
Expect(serviceMonitor.Spec.Selector.MatchLabels).Should(Equal(GetOAMServiceLabel()))
|
||||
Expect(serviceMonitor.Spec.Selector.MatchExpressions).Should(BeNil())
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.MatchNames).Should(Equal([]string{metricsTrait.Namespace}))
|
||||
Expect(serviceMonitor.Spec.NamespaceSelector.Any).Should(BeFalse())
|
||||
Expect(len(serviceMonitor.Spec.Endpoints)).Should(Equal(1))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Port).Should(BeEmpty())
|
||||
Expect(*serviceMonitor.Spec.Endpoints[0].TargetPort).Should(BeEquivalentTo(targetPort))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Scheme).Should(Equal(scheme))
|
||||
Expect(serviceMonitor.Spec.Endpoints[0].Path).Should(Equal(metricsPath))
|
||||
})
|
||||
})
|
||||
@@ -1,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())
|
||||
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// TypeNginx is a type of route implementation using [Nginx-Ingress](https://github.com/kubernetes/ingress-nginx)
|
||||
const TypeNginx = "nginx"
|
||||
|
||||
// TypeContour is a type of route implementation using [contour ingress](https://github.com/projectcontour/contour)
|
||||
const TypeContour = "contour"
|
||||
|
||||
const (
|
||||
// StatusReady represents status is ready
|
||||
StatusReady = "Ready"
|
||||
// StatusSynced represents status is synced, this mean the controller has reconciled but not ready
|
||||
StatusSynced = "Synced"
|
||||
)
|
||||
|
||||
// RouteIngress is an interface of route ingress implementation
|
||||
type RouteIngress interface {
|
||||
Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress
|
||||
CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runtimev1alpha1.Condition)
|
||||
}
|
||||
|
||||
// GetRouteIngress will get real implementation from type, we could support more in the future.
|
||||
func GetRouteIngress(provider string, client client.Client) (RouteIngress, error) {
|
||||
var routeIngress RouteIngress
|
||||
switch provider {
|
||||
case TypeNginx, "":
|
||||
routeIngress = &Nginx{Client: client}
|
||||
case TypeContour:
|
||||
routeIngress = &Contour{Client: client}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknow route ingress provider '%v', only '%s' is supported now", provider, TypeNginx)
|
||||
}
|
||||
return routeIngress, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetRouteIngress(t *testing.T) {
|
||||
_, err := GetRouteIngress("nginx", nil)
|
||||
assert.NoError(t, err)
|
||||
_, err = GetRouteIngress("", nil)
|
||||
assert.NoError(t, err)
|
||||
_, err = GetRouteIngress("istio", nil)
|
||||
assert.EqualError(t, err, "unknow route ingress provider 'istio', only 'nginx' is supported now")
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
cmmeta "github.com/wonderflow/cert-manager-api/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Contour is Contour ingress implementation
|
||||
type Contour struct {
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
var _ RouteIngress = &Contour{}
|
||||
|
||||
// CheckStatus will check status of the ingress
|
||||
func (n *Contour) CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runtimev1alpha1.Condition) {
|
||||
ctx := context.Background()
|
||||
// check issuer
|
||||
if routeTrait.Spec.TLS != nil && routeTrait.Spec.TLS.Type != standardv1alpha1.ClusterIssuer {
|
||||
tls := routeTrait.Spec.TLS
|
||||
var issuer certmanager.Issuer
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: tls.IssuerName}, &issuer)
|
||||
if err != nil || len(issuer.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("issuer '%v' is pending to be resolved by controller", tls.IssuerName)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
condition := issuer.Status.Conditions[0]
|
||||
if condition.Status != cmmeta.ConditionTrue {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(condition.Reason),
|
||||
Message: condition.Message}}
|
||||
}
|
||||
}
|
||||
// check ingress
|
||||
ingresses := n.Construct(routeTrait)
|
||||
for _, in := range ingresses {
|
||||
|
||||
// Check Certificate
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var cert certmanager.Certificate
|
||||
// check cert
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: in.Name + "-cert"}, &cert)
|
||||
if err != nil || len(cert.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("CertificateRequest %s is pending to be resolved by controller", in.Name)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
certcondition := cert.Status.Conditions[0]
|
||||
if certcondition.Status != cmmeta.ConditionTrue || certcondition.Type != certmanager.CertificateConditionReady {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(certcondition.Reason),
|
||||
Message: certcondition.Message}}
|
||||
}
|
||||
}
|
||||
|
||||
// Check Ingress
|
||||
var ingress v1beta1.Ingress
|
||||
if err := n.Client.Get(ctx, types.NamespacedName{Namespace: in.Namespace, Name: in.Name}, &ingress); err != nil {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: err.Error()}}
|
||||
}
|
||||
ingressvalue := ingress.Status.LoadBalancer.Ingress
|
||||
if len(ingressvalue) < 1 || (ingressvalue[0].IP == "" && ingressvalue[0].Hostname == "") {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonCreating,
|
||||
Message: fmt.Sprintf("IP/Hostname of %s ingress is generating", in.Name)}}
|
||||
}
|
||||
}
|
||||
return StatusReady, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeReady, Status: v1.ConditionTrue,
|
||||
Reason: runtimev1alpha1.ReasonAvailable, LastTransitionTime: metav1.Now()}}
|
||||
}
|
||||
|
||||
// Construct will construct ingress from route
|
||||
func (*Contour) Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress {
|
||||
|
||||
// Don't create ingress if no host set, this is used for local K8s cluster demo and the route trait will create K8s service only.
|
||||
if routeTrait.Spec.Host == "" || strings.Contains(routeTrait.Spec.Host, "localhost") || strings.Contains(routeTrait.Spec.Host, "127.0.0.1") {
|
||||
return nil
|
||||
}
|
||||
var ingresses []*v1beta1.Ingress
|
||||
for idx, rule := range routeTrait.Spec.Rules {
|
||||
name := rule.Name
|
||||
if name == "" {
|
||||
name = strconv.Itoa(idx)
|
||||
}
|
||||
backend := rule.Backend
|
||||
if backend == nil || backend.BackendService == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var annotations = make(map[string]string)
|
||||
|
||||
annotations["kubernetes.io/ingress.class"] = TypeContour
|
||||
|
||||
// SSL
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var issuerAnn = "cert-manager.io/issuer"
|
||||
if routeTrait.Spec.TLS.Type == standardv1alpha1.ClusterIssuer {
|
||||
issuerAnn = "cert-manager.io/cluster-issuer"
|
||||
}
|
||||
annotations[issuerAnn] = routeTrait.Spec.TLS.IssuerName
|
||||
}
|
||||
// todo Rewrite
|
||||
|
||||
// todo Custom headers
|
||||
|
||||
// todo Send timeout
|
||||
|
||||
// Read timeout
|
||||
if backend.ReadTimeout != 0 {
|
||||
annotations["projectcontour.io/response-timeout"] = strconv.Itoa(backend.ReadTimeout)
|
||||
}
|
||||
|
||||
ingress := &v1beta1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: routeTrait.Name + "-" + name,
|
||||
Namespace: routeTrait.Namespace,
|
||||
Annotations: annotations,
|
||||
Labels: routeTrait.GetLabels(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: routeTrait.GetUID(),
|
||||
Name: routeTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
ingress.Spec.TLS = []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{routeTrait.Spec.Host},
|
||||
SecretName: routeTrait.Name + "-" + name + "-cert",
|
||||
},
|
||||
}
|
||||
}
|
||||
if rule.DefaultBackend != nil {
|
||||
ingress.Spec.Backend = &v1beta1.IngressBackend{
|
||||
Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: &rule.DefaultBackend.APIVersion,
|
||||
Kind: rule.DefaultBackend.Kind,
|
||||
Name: rule.DefaultBackend.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
ingress.Spec.Rules = []v1beta1.IngressRule{
|
||||
{
|
||||
Host: routeTrait.Spec.Host,
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{
|
||||
Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: rule.Path,
|
||||
Backend: v1beta1.IngressBackend{
|
||||
ServiceName: backend.BackendService.ServiceName,
|
||||
ServicePort: backend.BackendService.Port,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
ingresses = append(ingresses, ingress)
|
||||
}
|
||||
return ingresses
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
func TestContourConstruct(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
routeTrait *standardv1alpha1.Route
|
||||
exp []*v1beta1.Ingress
|
||||
}{
|
||||
"normal case": {
|
||||
routeTrait: &standardv1alpha1.Route{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Route",
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test",
|
||||
},
|
||||
Spec: standardv1alpha1.RouteSpec{
|
||||
Host: "test.abc",
|
||||
TLS: &standardv1alpha1.TLS{
|
||||
IssuerName: "test-issuer",
|
||||
Type: "Issuer",
|
||||
},
|
||||
Rules: []standardv1alpha1.Rule{
|
||||
{
|
||||
Name: "myrule1",
|
||||
Backend: &standardv1alpha1.Backend{BackendService: &standardv1alpha1.BackendServiceRef{ServiceName: "test", Port: intstr.FromInt(3030)}},
|
||||
DefaultBackend: &v1alpha1.TypedReference{
|
||||
APIVersion: "k8s.example.com/v1",
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
exp: []*v1beta1.Ingress{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Ingress",
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test-myrule1",
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "contour",
|
||||
"cert-manager.io/issuer": "test-issuer",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
Kind: "Route",
|
||||
Name: "trait-test",
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.IngressSpec{
|
||||
TLS: []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"test.abc"},
|
||||
SecretName: "trait-test-myrule1-cert",
|
||||
},
|
||||
},
|
||||
Backend: &v1beta1.IngressBackend{Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: pointer.StringPtr("k8s.example.com/v1"),
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
}},
|
||||
Rules: []v1beta1.IngressRule{
|
||||
{
|
||||
Host: "test.abc",
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: "",
|
||||
Backend: v1beta1.IngressBackend{ServiceName: "test", ServicePort: intstr.FromInt(3030)},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for message, ti := range tests {
|
||||
contour := &Contour{}
|
||||
got := contour.Construct(ti.routeTrait)
|
||||
assert.Equal(t, len(ti.exp), len(got))
|
||||
for idx := range ti.exp {
|
||||
assert.Equal(t, ti.exp[idx], got[idx], message+" index "+strconv.Itoa(idx))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
cmmeta "github.com/wonderflow/cert-manager-api/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Nginx is nginx ingress implementation
|
||||
type Nginx struct {
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
var _ RouteIngress = &Nginx{}
|
||||
|
||||
// CheckStatus will check status of the ingress
|
||||
func (n *Nginx) CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runtimev1alpha1.Condition) {
|
||||
ctx := context.Background()
|
||||
// check issuer
|
||||
if routeTrait.Spec.TLS != nil && routeTrait.Spec.TLS.Type != standardv1alpha1.ClusterIssuer {
|
||||
tls := routeTrait.Spec.TLS
|
||||
var issuer certmanager.Issuer
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: tls.IssuerName}, &issuer)
|
||||
if err != nil || len(issuer.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("issuer '%v' is pending to be resolved by controller", tls.IssuerName)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
condition := issuer.Status.Conditions[0]
|
||||
if condition.Status != cmmeta.ConditionTrue {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(condition.Reason),
|
||||
Message: condition.Message}}
|
||||
}
|
||||
}
|
||||
// check ingress
|
||||
ingresses := n.Construct(routeTrait)
|
||||
for _, in := range ingresses {
|
||||
|
||||
// Check Certificate
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var cert certmanager.Certificate
|
||||
// check cert
|
||||
err := n.Client.Get(ctx, types.NamespacedName{Namespace: routeTrait.Namespace, Name: in.Name + "-cert"}, &cert)
|
||||
if err != nil || len(cert.Status.Conditions) < 1 {
|
||||
var message string
|
||||
if err == nil {
|
||||
message = fmt.Sprintf("CertificateRequest %s is pending to be resolved by controller", in.Name)
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: message}}
|
||||
}
|
||||
// TODO(wonderflow): handle more than one condition case
|
||||
certcondition := cert.Status.Conditions[0]
|
||||
if certcondition.Status != cmmeta.ConditionTrue || certcondition.Type != certmanager.CertificateConditionReady {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ConditionReason(certcondition.Reason),
|
||||
Message: certcondition.Message}}
|
||||
}
|
||||
}
|
||||
|
||||
// Check Ingress
|
||||
var ingress v1beta1.Ingress
|
||||
if err := n.Client.Get(ctx, types.NamespacedName{Namespace: in.Namespace, Name: in.Name}, &ingress); err != nil {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonUnavailable,
|
||||
Message: err.Error()}}
|
||||
}
|
||||
ingressvalue := ingress.Status.LoadBalancer.Ingress
|
||||
if len(ingressvalue) < 1 || (ingressvalue[0].IP == "" && ingressvalue[0].Hostname == "") {
|
||||
return StatusSynced, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeSynced,
|
||||
Status: v1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: runtimev1alpha1.ReasonCreating,
|
||||
Message: fmt.Sprintf("IP/Hostname of %s ingress is generating", in.Name)}}
|
||||
}
|
||||
}
|
||||
return StatusReady, []runtimev1alpha1.Condition{{Type: runtimev1alpha1.TypeReady, Status: v1.ConditionTrue,
|
||||
Reason: runtimev1alpha1.ReasonAvailable, LastTransitionTime: metav1.Now()}}
|
||||
}
|
||||
|
||||
// Construct will construct ingress from route
|
||||
func (*Nginx) Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress {
|
||||
|
||||
// Don't create ingress if no host set, this is used for local K8s cluster demo and the route trait will create K8s service only.
|
||||
if routeTrait.Spec.Host == "" || strings.Contains(routeTrait.Spec.Host, "localhost") || strings.Contains(routeTrait.Spec.Host, "127.0.0.1") {
|
||||
return nil
|
||||
}
|
||||
var ingresses []*v1beta1.Ingress
|
||||
for idx, rule := range routeTrait.Spec.Rules {
|
||||
name := rule.Name
|
||||
if name == "" {
|
||||
name = strconv.Itoa(idx)
|
||||
}
|
||||
backend := rule.Backend
|
||||
if backend == nil || backend.BackendService == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var annotations = make(map[string]string)
|
||||
|
||||
annotations["kubernetes.io/ingress.class"] = routeTrait.Spec.IngressClass
|
||||
|
||||
// SSL
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
var issuerAnn = "cert-manager.io/issuer"
|
||||
if routeTrait.Spec.TLS.Type == standardv1alpha1.ClusterIssuer {
|
||||
issuerAnn = "cert-manager.io/cluster-issuer"
|
||||
}
|
||||
annotations[issuerAnn] = routeTrait.Spec.TLS.IssuerName
|
||||
}
|
||||
// Rewrite
|
||||
if rule.RewriteTarget != "" {
|
||||
annotations["nginx.ingress.kubernetes.io/rewrite-target"] = rule.RewriteTarget
|
||||
}
|
||||
|
||||
// Custom headers
|
||||
var headerSnippet string
|
||||
for k, v := range rule.CustomHeaders {
|
||||
headerSnippet += fmt.Sprintf("more_set_headers \"%s: %s\";\n", k, v)
|
||||
}
|
||||
if headerSnippet != "" {
|
||||
annotations["nginx.ingress.kubernetes.io/configuration-snippet"] = headerSnippet
|
||||
}
|
||||
|
||||
// Send timeout
|
||||
if backend.SendTimeout != 0 {
|
||||
annotations["nginx.ingress.kubernetes.io/proxy-send-timeout"] = strconv.Itoa(backend.SendTimeout)
|
||||
}
|
||||
|
||||
// Read timeout
|
||||
if backend.ReadTimeout != 0 {
|
||||
annotations["nginx.ingress.kubernetes.io/proxy‑read‑timeout"] = strconv.Itoa(backend.ReadTimeout)
|
||||
}
|
||||
|
||||
ingress := &v1beta1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: routeTrait.Name + "-" + name,
|
||||
Namespace: routeTrait.Namespace,
|
||||
Annotations: annotations,
|
||||
Labels: routeTrait.GetLabels(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: routeTrait.GetUID(),
|
||||
Name: routeTrait.GetName(),
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if routeTrait.Spec.TLS != nil {
|
||||
ingress.Spec.TLS = []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{routeTrait.Spec.Host},
|
||||
SecretName: routeTrait.Name + "-" + name + "-cert",
|
||||
},
|
||||
}
|
||||
}
|
||||
if rule.DefaultBackend != nil {
|
||||
ingress.Spec.Backend = &v1beta1.IngressBackend{
|
||||
Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: &rule.DefaultBackend.APIVersion,
|
||||
Kind: rule.DefaultBackend.Kind,
|
||||
Name: rule.DefaultBackend.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
ingress.Spec.Rules = []v1beta1.IngressRule{
|
||||
{
|
||||
Host: routeTrait.Spec.Host,
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{
|
||||
Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: rule.Path,
|
||||
Backend: v1beta1.IngressBackend{
|
||||
ServiceName: backend.BackendService.ServiceName,
|
||||
ServicePort: backend.BackendService.Port,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
ingresses = append(ingresses, ingress)
|
||||
}
|
||||
return ingresses
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
func TestConstruct(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
routeTrait *standardv1alpha1.Route
|
||||
exp []*v1beta1.Ingress
|
||||
}{
|
||||
"normal case": {
|
||||
routeTrait: &standardv1alpha1.Route{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Route",
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test",
|
||||
},
|
||||
Spec: standardv1alpha1.RouteSpec{
|
||||
Host: "test.abc",
|
||||
TLS: &standardv1alpha1.TLS{
|
||||
IssuerName: "test-issuer",
|
||||
Type: "Issuer",
|
||||
},
|
||||
Rules: []standardv1alpha1.Rule{
|
||||
{
|
||||
Name: "myrule1",
|
||||
Backend: &standardv1alpha1.Backend{BackendService: &standardv1alpha1.BackendServiceRef{ServiceName: "test", Port: intstr.FromInt(3030)}},
|
||||
DefaultBackend: &v1alpha1.TypedReference{
|
||||
APIVersion: "k8s.example.com/v1",
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
},
|
||||
},
|
||||
},
|
||||
IngressClass: "nginx-private",
|
||||
},
|
||||
},
|
||||
exp: []*v1beta1.Ingress{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Ingress",
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "trait-test-myrule1",
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "nginx-private",
|
||||
"cert-manager.io/issuer": "test-issuer",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
Kind: "Route",
|
||||
Name: "trait-test",
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.IngressSpec{
|
||||
TLS: []v1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"test.abc"},
|
||||
SecretName: "trait-test-myrule1-cert",
|
||||
},
|
||||
},
|
||||
Backend: &v1beta1.IngressBackend{Resource: &v1.TypedLocalObjectReference{
|
||||
APIGroup: pointer.StringPtr("k8s.example.com/v1"),
|
||||
Kind: "StorageBucket",
|
||||
Name: "static-assets",
|
||||
}},
|
||||
Rules: []v1beta1.IngressRule{
|
||||
{
|
||||
Host: "test.abc",
|
||||
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{Paths: []v1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: "",
|
||||
Backend: v1beta1.IngressBackend{ServiceName: "test", ServicePort: intstr.FromInt(3030)},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for message, ti := range tests {
|
||||
nginx := &Nginx{}
|
||||
got := nginx.Construct(ti.routeTrait)
|
||||
assert.Equal(t, len(ti.exp), len(got))
|
||||
for idx := range ti.exp {
|
||||
assert.Equal(t, ti.exp[idx], got[idx], message+" index "+strconv.Itoa(idx))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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)
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Route Trait Integration Test", func() {
|
||||
// common var init
|
||||
ctx := context.Background()
|
||||
namespaceName := "routetrait-integration-test"
|
||||
|
||||
podPort := 8000
|
||||
issuerName := "my-issuer"
|
||||
|
||||
var ns corev1.Namespace
|
||||
getComponent := func(workloadType, compName string) (v1alpha2.Component, map[string]string, map[string]string) {
|
||||
podTemplateLabel := map[string]string{"standard.oam.dev": "oam-test-deployment", "workload.oam.dev/type": workloadType}
|
||||
workloadLabel := map[string]string{"standard.oam.dev": "oam-test-deployment", "app.oam.dev/component": compName, "app.oam.dev/name": "test-app-" + compName}
|
||||
basedeploy := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: podTemplateLabel,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: podTemplateLabel,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: podTemplateLabel,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container-name",
|
||||
Image: "crccheck/hello-world",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(podPort),
|
||||
}}}}}}},
|
||||
}
|
||||
var rp = int32(1)
|
||||
if workloadType == "webservice" {
|
||||
basePodSpecc := &v1alpha1.PodSpecWorkload{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PodSpecWorkload",
|
||||
APIVersion: "standard.oam.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: podTemplateLabel,
|
||||
},
|
||||
Spec: v1alpha1.PodSpecWorkloadSpec{
|
||||
Replicas: &rp,
|
||||
PodSpec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container-name",
|
||||
Image: "crccheck/hello-world",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(podPort),
|
||||
}}}}}},
|
||||
}
|
||||
return v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: ns.Name,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{Object: basePodSpecc},
|
||||
},
|
||||
}, workloadLabel, podTemplateLabel
|
||||
}
|
||||
return v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: ns.Name,
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: runtime.RawExtension{Object: basedeploy},
|
||||
},
|
||||
}, workloadLabel, podTemplateLabel
|
||||
}
|
||||
|
||||
getAC := func(compName string) v1alpha2.ApplicationConfiguration {
|
||||
return v1alpha2.ApplicationConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ApplicationConfiguration",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns.Name,
|
||||
Name: "test-app-" + compName,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: runtime.RawExtension{Object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "standard.oam.dev/v1alpha1",
|
||||
"kind": "Route",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
oam.TraitTypeLabel: "route",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"host": "mycomp.mytest.com",
|
||||
"tls": map[string]interface{}{
|
||||
"issuerName": issuerName,
|
||||
}}}}}}}}}}}
|
||||
}
|
||||
BeforeEach(func() {
|
||||
logf.Log.Info("[TEST] Set up resources before an integration test")
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespaceName,
|
||||
},
|
||||
}
|
||||
|
||||
By("Create the Namespace for test")
|
||||
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
By("Create the Issuer for test")
|
||||
Expect(k8sClient.Create(context.Background(), &certmanager.Issuer{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: issuerName, Namespace: namespaceName},
|
||||
Spec: certmanager.IssuerSpec{IssuerConfig: certmanager.IssuerConfig{SelfSigned: &certmanager.SelfSignedIssuer{}}},
|
||||
})).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Control-runtime test environment has a bug that can't delete resources like deployment/namespaces
|
||||
// We have to use different names to segregate between tests
|
||||
logf.Log.Info("[TEST] Clean up resources after an integration test")
|
||||
})
|
||||
|
||||
It("Test with child resource no podSpecable but has service child using webservice workload", func() {
|
||||
compName := "test-webservice"
|
||||
comp, _, _ := getComponent("webservice", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() error {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return errors.New("workload or trait not ready")
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(createdRoute.Status.Ingresses) == 0 {
|
||||
return errors.New("no ingress created")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
|
||||
By("Check that we have created the ingress")
|
||||
createdIngress := v1beta1.Ingress{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: createdRoute.Status.Ingresses[0].Name},
|
||||
&createdIngress)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created ingress", "ingress rules", createdIngress.Spec.Rules)
|
||||
Expect(createdIngress.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(len(createdIngress.Spec.Rules)).Should(Equal(1))
|
||||
Expect(createdIngress.Spec.Rules[0].Host).Should(Equal("mycomp.mytest.com"))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName).Should(Equal(compName))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal).Should(Equal(int32(8080)))
|
||||
})
|
||||
It("Test with podSpec label with no podSpecPath using deployment workload", func() {
|
||||
compName := "test-deployment"
|
||||
comp, _, deploylabel := getComponent("deployment", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() error {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return errors.New("workload or trait not ready")
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(createdRoute.Status.Ingresses) == 0 {
|
||||
return errors.New("no ingress created")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
|
||||
By("Check that we have created the ingress")
|
||||
createdIngress := v1beta1.Ingress{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: createdRoute.Status.Ingresses[0].Name},
|
||||
&createdIngress)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created ingress", "ingress rules", createdIngress.Spec.Rules)
|
||||
Expect(createdIngress.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(len(createdIngress.Spec.Rules)).Should(Equal(1))
|
||||
Expect(createdIngress.Spec.Rules[0].Host).Should(Equal("mycomp.mytest.com"))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName).Should(Equal(traitName))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal).Should(Equal(int32(8000)))
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdSvc := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdSvc)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdSvc.Spec.Ports)
|
||||
Expect(createdSvc.Spec.Selector).Should(Equal(deploylabel))
|
||||
Expect(createdSvc.Spec.Ports[0].TargetPort.IntVal).Should(Equal(int32(podPort)))
|
||||
})
|
||||
It("Test with podSpecPath specified using deploy workload", func() {
|
||||
compName := "test-deploy"
|
||||
comp, _, _ := getComponent("deploy", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() error {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return errors.New("workload or trait not ready")
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(createdRoute.Status.Ingresses) == 0 {
|
||||
return errors.New("no ingress created")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
|
||||
By("Check that we have created the ingress")
|
||||
createdIngress := v1beta1.Ingress{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: createdRoute.Status.Ingresses[0].Name},
|
||||
&createdIngress)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created ingress", "ingress rules", createdIngress.Spec.Rules)
|
||||
Expect(createdIngress.GetNamespace()).Should(Equal(namespaceName))
|
||||
Expect(len(createdIngress.Spec.Rules)).Should(Equal(1))
|
||||
Expect(createdIngress.Spec.Rules[0].Host).Should(Equal("mycomp.mytest.com"))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName).Should(Equal(traitName))
|
||||
Expect(createdIngress.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort.IntVal).Should(Equal(int32(8000)))
|
||||
|
||||
By("Check that we have created the service")
|
||||
createdSvc := corev1.Service{}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdSvc)
|
||||
},
|
||||
time.Second*30, time.Millisecond*500).Should(BeNil())
|
||||
logf.Log.Info("[TEST] Get the created service", "service ports", createdSvc.Spec.Ports)
|
||||
for k, v := range map[string]string{"app.oam.dev/component": compName, "app.oam.dev/name": "test-app-" + compName} {
|
||||
Expect(createdSvc.Spec.Selector).Should(HaveKeyWithValue(k, v))
|
||||
}
|
||||
Expect(createdSvc.Spec.Ports[0].TargetPort.IntVal).Should(Equal(int32(podPort)))
|
||||
})
|
||||
It("Test should get error condition if definition not found", func() {
|
||||
compName := "test-no-def"
|
||||
comp, _, _ := getComponent("unknow1", compName)
|
||||
ac := getAC(compName)
|
||||
Expect(k8sClient.Create(ctx, &comp)).ToNot(HaveOccurred())
|
||||
Expect(k8sClient.Create(ctx, &ac)).ToNot(HaveOccurred())
|
||||
|
||||
By("Check that we have created the route")
|
||||
createdRoute := v1alpha1.Route{}
|
||||
var traitName string
|
||||
Eventually(
|
||||
func() string {
|
||||
err := k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: ac.Name},
|
||||
&ac)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(ac.Status.Workloads) < 1 || len(ac.Status.Workloads[0].Traits) < 1 {
|
||||
return "workload or trait not ready"
|
||||
}
|
||||
traitName = ac.Status.Workloads[0].Traits[0].Reference.Name
|
||||
err = k8sClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: ns.Name, Name: traitName},
|
||||
&createdRoute)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if len(createdRoute.Status.Conditions) == 1 {
|
||||
return createdRoute.Status.Conditions[0].Message
|
||||
}
|
||||
return ""
|
||||
},
|
||||
time.Second*10, time.Millisecond*500).Should(Equal(`failed to create the services: WorkloadDefinition.core.oam.dev "unknow1" not found`))
|
||||
})
|
||||
})
|
||||
@@ -1,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())
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package routes
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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: []
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user