mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
add the metrics trait
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,6 +31,7 @@ vendor/
|
||||
.vscode
|
||||
|
||||
pkg/test/vela
|
||||
config/crd/bases
|
||||
tmp/
|
||||
|
||||
# Dashboard
|
||||
|
||||
1
Makefile
1
Makefile
@@ -91,7 +91,6 @@ core-deploy: manifests
|
||||
manifests: controller-gen
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
|
||||
|
||||
# Generate code
|
||||
generate: controller-gen
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
36
api/v1alpha1/groupversion_info.go
Normal file
36
api/v1alpha1/groupversion_info.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
|
||||
|
||||
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 contains API Schema definitions for the standard v1alpha1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=standard.oam.dev
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: "standard.oam.dev", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
114
api/v1alpha1/metricstrait_types.go
Normal file
114
api/v1alpha1/metricstrait_types.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
|
||||
|
||||
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"
|
||||
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// When this field has value implies that we need to create a service for the workload
|
||||
// Mutually exclusive with port.
|
||||
TargetPort intstr.IntOrString `json:"port,omitempty"`
|
||||
// Route service traffic to pods with label keys and values matching this
|
||||
// The default is the labels in the workload
|
||||
// Mutually exclusive with port.
|
||||
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"`
|
||||
|
||||
// ServiceMonitorNames managed by this trait
|
||||
ServiceMonitorNames []string `json:"serviceMonitorName,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{}
|
||||
|
||||
func (in *MetricsTrait) SetConditions(c ...runtimev1alpha1.Condition) {
|
||||
in.Status.SetConditions(c...)
|
||||
}
|
||||
|
||||
func (in *MetricsTrait) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
|
||||
return in.Status.GetCondition(c)
|
||||
}
|
||||
|
||||
// GetWorkloadReference of this ManualScalerTrait.
|
||||
func (tr *MetricsTrait) GetWorkloadReference() runtimev1alpha1.TypedReference {
|
||||
return tr.Spec.WorkloadReference
|
||||
}
|
||||
|
||||
// SetWorkloadReference of this ManualScalerTrait.
|
||||
func (tr *MetricsTrait) SetWorkloadReference(r runtimev1alpha1.TypedReference) {
|
||||
tr.Spec.WorkloadReference = r
|
||||
}
|
||||
150
api/v1alpha1/zz_generated.deepcopy.go
Normal file
150
api/v1alpha1/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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)
|
||||
if in.ServiceMonitorNames != nil {
|
||||
in, out := &in.ServiceMonitorNames, &out.ServiceMonitorNames
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *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
|
||||
}
|
||||
@@ -15,8 +15,9 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
|
||||
controller "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
|
||||
webhook "github.com/crossplane/oam-kubernetes-runtime/pkg/webhook/v1alpha2"
|
||||
oamcontroller "github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
|
||||
oamv1alpha2 "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
|
||||
oamwebhook "github.com/crossplane/oam-kubernetes-runtime/pkg/webhook/v1alpha2"
|
||||
)
|
||||
|
||||
var scheme = runtime.NewScheme()
|
||||
@@ -34,6 +35,7 @@ func main() {
|
||||
var certDir string
|
||||
var webhookPort int
|
||||
var useWebhook bool
|
||||
var controllerArgs oamcontroller.Args
|
||||
|
||||
flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook")
|
||||
flag.StringVar(&certDir, "webhook-cert-dir", "/k8s-webhook-server/serving-certs", "Admission webhook cert/key dir.")
|
||||
@@ -44,6 +46,8 @@ func main() {
|
||||
flag.StringVar(&logFilePath, "log-file-path", "", "The address the metric endpoint binds to.")
|
||||
flag.IntVar(&logRetainDate, "log-retain-date", 7, "The number of days of logs history to retain.")
|
||||
flag.BoolVar(&logCompress, "log-compress", true, "Enable compression on the rotated logs.")
|
||||
flag.IntVar(&controllerArgs.RevisionLimit, "revision-limit", 50,
|
||||
"RevisionLimit is the maximum number of revisions that will be maintained. The default value is 50.")
|
||||
flag.Parse()
|
||||
|
||||
// setup logging
|
||||
@@ -63,12 +67,12 @@ func main() {
|
||||
o.DestWritter = w
|
||||
}))
|
||||
|
||||
oamLog := ctrl.Log.WithName("vela-core")
|
||||
oamLog := ctrl.Log.WithName("oam-kubernetes-runtime")
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "vela-core",
|
||||
LeaderElectionID: "oam-kubernetes-runtime",
|
||||
Port: webhookPort,
|
||||
CertDir: certDir,
|
||||
})
|
||||
@@ -79,10 +83,10 @@ func main() {
|
||||
|
||||
if useWebhook {
|
||||
oamLog.Info("OAM webhook enabled, will serving at :" + strconv.Itoa(webhookPort))
|
||||
webhook.Add(mgr)
|
||||
oamwebhook.Add(mgr)
|
||||
}
|
||||
|
||||
if err = controller.Setup(mgr, logging.NewLogrLogger(oamLog)); err != nil {
|
||||
if err = oamv1alpha2.Setup(mgr, controllerArgs, logging.NewLogrLogger(oamLog)); err != nil {
|
||||
oamLog.Error(err, "unable to setup the oam core controller")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
76
config/rbac/role.yaml
Normal file
76
config/rbac/role.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- core.oam.dev
|
||||
resources:
|
||||
- '*'
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- core.oam.dev
|
||||
resources:
|
||||
- '*/status'
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- '*'
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- '*/status'
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- standard.oam.dev
|
||||
resources:
|
||||
- metricstraits
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- standard.oam.dev
|
||||
resources:
|
||||
- metricstraits/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
53
config/webhook/manifests.yaml
Normal file
53
config/webhook/manifests.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: mutating-webhook-configuration
|
||||
webhooks:
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /mutate-standard-oam-dev-v1alpha1-metricstrait
|
||||
failurePolicy: Fail
|
||||
name: mmetricstrait.kb.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- standard.oam.dev
|
||||
apiVersions:
|
||||
- v1alpha1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- metricstraits
|
||||
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: validating-webhook-configuration
|
||||
webhooks:
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validate-standard-oam-dev-v1alpha1-metricstrait
|
||||
failurePolicy: Fail
|
||||
name: vmetricstrait.kb.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- standard.oam.dev
|
||||
apiVersions:
|
||||
- v1alpha1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- metricstraits
|
||||
20
go.mod
20
go.mod
@@ -4,24 +4,26 @@ go 1.13
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.2.2
|
||||
github.com/crossplane/crossplane-runtime v0.8.0
|
||||
github.com/crossplane/oam-kubernetes-runtime v0.0.8
|
||||
github.com/coreos/prometheus-operator v0.41.1
|
||||
github.com/crossplane/crossplane-runtime v0.9.0
|
||||
github.com/crossplane/oam-kubernetes-runtime v0.0.9
|
||||
github.com/gertd/go-pluralize v0.1.7
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-logr/logr v0.1.0
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/google/go-github/v32 v32.1.0
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/oam-dev/catalog/traits/metricstrait v0.0.0-20200826071236-d96c1d64e221
|
||||
github.com/onsi/ginkgo v1.11.0
|
||||
github.com/onsi/gomega v1.8.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.uber.org/zap v1.10.0
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
|
||||
go.uber.org/zap v1.13.0
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
helm.sh/helm/v3 v3.2.4
|
||||
@@ -29,9 +31,13 @@ require (
|
||||
k8s.io/apiextensions-apiserver v0.18.2
|
||||
k8s.io/apimachinery v0.18.6
|
||||
k8s.io/cli-runtime v0.18.6
|
||||
k8s.io/client-go v0.18.6
|
||||
k8s.io/client-go v12.0.0+incompatible
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/kubectl v0.18.6 // indirect
|
||||
k8s.io/utils v0.0.0-20200414100711-2df71ebbae66
|
||||
rsc.io/letsencrypt v0.0.3 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.6.0
|
||||
)
|
||||
|
||||
// clint-go had a buggy release, https://github.com/kubernetes/client-go/issues/749
|
||||
replace k8s.io/client-go => k8s.io/client-go v0.18.6
|
||||
|
||||
307
pkg/controller/metrics/metricstrait_controller.go
Normal file
307
pkg/controller/metrics/metricstrait_controller.go
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
|
||||
|
||||
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"
|
||||
oamutil "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
|
||||
"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/util/intstr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
errApplyServiceMonitor = "failed to apply the service monitor"
|
||||
errLocatingService = "failed to locate any the services"
|
||||
servicePort = 4848
|
||||
)
|
||||
|
||||
var (
|
||||
serviceMonitorKind = reflect.TypeOf(monitoring.ServiceMonitor{}).Name()
|
||||
serviceMonitorAPIVersion = monitoring.SchemeGroupVersion.String()
|
||||
serviceKind = reflect.TypeOf(corev1.Service{}).Name()
|
||||
serviceAPIVersion = corev1.SchemeGroupVersion.String()
|
||||
trueVar = true
|
||||
)
|
||||
|
||||
var (
|
||||
// oamServiceLabel is the pre-defined labels for any serviceMonitor
|
||||
// created by the MetricsTrait, prometheus operator listens on this
|
||||
oamServiceLabel = map[string]string{
|
||||
"k8s-app": "oam",
|
||||
"controller": "metricsTrait",
|
||||
}
|
||||
// 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 = "oam-monitoring"
|
||||
)
|
||||
|
||||
// MetricsTraitReconciler reconciles a MetricsTrait object
|
||||
type MetricsTraitReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
}
|
||||
|
||||
// +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 *MetricsTraitReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
ctx := context.Background()
|
||||
mLog := r.Log.WithValues("metricstrait", req.NamespacedName)
|
||||
mLog.Info("Reconcile metricstrait trait")
|
||||
// fetch the trait
|
||||
var metricsTrait v1alpha1.MetricsTrait
|
||||
if err := r.Get(ctx, req.NamespacedName, &metricsTrait); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
mLog.Info("Get the metricsTrait trait",
|
||||
"metrics end point", metricsTrait.Spec.ScrapeService,
|
||||
"workload reference", metricsTrait.Spec.WorkloadReference,
|
||||
"labels", metricsTrait.GetLabels())
|
||||
|
||||
// find the resource object to record the event to, default is the parent appConfig.
|
||||
eventObj, err := oamutil.LocateParentAppConfig(ctx, r.Client, &metricsTrait)
|
||||
if eventObj == nil {
|
||||
// fallback to workload itself
|
||||
mLog.Error(err, "add events to metricsTrait itself", "name", metricsTrait.Name)
|
||||
eventObj = &metricsTrait
|
||||
}
|
||||
if metricsTrait.Spec.ScrapeService.Enabled != nil && !*metricsTrait.Spec.ScrapeService.Enabled {
|
||||
r.record.Event(eventObj, event.Normal("Metrics Trait disabled", "no op"))
|
||||
r.gcOrphanServiceMonitor(ctx, mLog, &metricsTrait)
|
||||
return ctrl.Result{}, oamutil.PatchCondition(ctx, r, &metricsTrait, cpv1alpha1.ReconcileSuccess())
|
||||
}
|
||||
|
||||
// Fetch the workload instance to which we want to expose metrics
|
||||
workload, err := oamutil.FetchWorkload(ctx, r, mLog, &metricsTrait)
|
||||
if err != nil {
|
||||
mLog.Error(err, "Error while fetching the workload", "workload reference",
|
||||
metricsTrait.GetWorkloadReference())
|
||||
r.record.Event(eventObj, event.Warning(errLocatingService, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, errLocatingService)))
|
||||
}
|
||||
// try to see if the workload already has services as child resources
|
||||
serviceLabel, err := r.fetchServicesLabel(ctx, mLog, workload, metricsTrait.Spec.ScrapeService.TargetPort)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
r.record.Event(eventObj, event.Warning(errLocatingService, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, 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, err = r.createService(ctx, mLog, workload, &metricsTrait)
|
||||
if err != nil {
|
||||
r.record.Event(eventObj, event.Warning(errLocatingService, err))
|
||||
return oamutil.ReconcileWaitResult,
|
||||
oamutil.PatchCondition(ctx, r, &metricsTrait,
|
||||
cpv1alpha1.ReconcileError(errors.Wrap(err, errLocatingService)))
|
||||
}
|
||||
}
|
||||
// construct the serviceMonitor that hooks the service to the prometheus server
|
||||
serviceMonitor := constructServiceMonitor(&metricsTrait, serviceLabel)
|
||||
// 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)
|
||||
|
||||
return ctrl.Result{}, oamutil.PatchCondition(ctx, r, &metricsTrait, cpv1alpha1.ReconcileSuccess())
|
||||
}
|
||||
|
||||
// fetch the label of the service that is associated with the workload
|
||||
func (r *MetricsTraitReconciler) 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, 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() == serviceAPIVersion && childRes.GetKind() == 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 *MetricsTraitReconciler) createService(ctx context.Context, mLog logr.Logger, workload *unstructured.Unstructured,
|
||||
metricsTrait *v1alpha1.MetricsTrait) (map[string]string, error) {
|
||||
oamService := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: serviceKind,
|
||||
APIVersion: serviceAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "oam-" + workload.GetName(),
|
||||
Namespace: workload.GetNamespace(),
|
||||
Labels: oamServiceLabel,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
},
|
||||
}
|
||||
// assign selector
|
||||
if len(metricsTrait.Spec.ScrapeService.TargetSelector) == 0 {
|
||||
// default is that we assumed that the pods have the same label as the workload
|
||||
// we might be able to find the podSpec label but it is more complicated
|
||||
oamService.Spec.Selector = workload.GetLabels()
|
||||
} else {
|
||||
oamService.Spec.Selector = metricsTrait.Spec.ScrapeService.TargetSelector
|
||||
}
|
||||
oamService.Spec.Ports = []corev1.ServicePort{
|
||||
{
|
||||
Port: servicePort,
|
||||
TargetPort: metricsTrait.Spec.ScrapeService.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, err
|
||||
}
|
||||
return oamServiceLabel, nil
|
||||
}
|
||||
|
||||
// remove all service monitors that are no longer used
|
||||
func (r *MetricsTraitReconciler) gcOrphanServiceMonitor(ctx context.Context, mLog logr.Logger,
|
||||
metricsTrait *v1alpha1.MetricsTrait) {
|
||||
var gcCandidates []string
|
||||
copy(metricsTrait.Status.ServiceMonitorNames, gcCandidates)
|
||||
if metricsTrait.Spec.ScrapeService.Enabled != nil && !*metricsTrait.Spec.ScrapeService.Enabled {
|
||||
// initialize it to be an empty list, gc everything
|
||||
metricsTrait.Status.ServiceMonitorNames = []string{}
|
||||
} else {
|
||||
// re-initialize to the current service monitor
|
||||
metricsTrait.Status.ServiceMonitorNames = []string{metricsTrait.Name}
|
||||
}
|
||||
for _, smn := range gcCandidates {
|
||||
if smn != metricsTrait.Name {
|
||||
if err := r.Delete(ctx, &monitoring.ServiceMonitor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: serviceMonitorKind,
|
||||
APIVersion: serviceMonitorAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: smn,
|
||||
Namespace: metricsTrait.GetNamespace(),
|
||||
},
|
||||
}, client.GracePeriodSeconds(10)); err != nil {
|
||||
mLog.Error(err, "Failed to delete serviceMonitor", "name", smn, "error", err)
|
||||
// add it back
|
||||
metricsTrait.Status.ServiceMonitorNames = append(metricsTrait.Status.ServiceMonitorNames, smn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// construct a serviceMonitor given a metrics trait along with a label selector pointing to the underlying service
|
||||
func constructServiceMonitor(metricsTrait *v1alpha1.MetricsTrait,
|
||||
serviceLabels map[string]string) *monitoring.ServiceMonitor {
|
||||
return &monitoring.ServiceMonitor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: serviceMonitorKind,
|
||||
APIVersion: serviceMonitorAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: metricsTrait.Name,
|
||||
Namespace: serviceMonitorNSName,
|
||||
Labels: oamServiceLabel,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: metricsTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
|
||||
Kind: metricsTrait.GetObjectKind().GroupVersionKind().Kind,
|
||||
UID: metricsTrait.GetUID(),
|
||||
Name: metricsTrait.GetName(),
|
||||
Controller: &trueVar,
|
||||
BlockOwnerDeletion: &trueVar,
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: monitoring.ServiceMonitorSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchLabels: serviceLabels,
|
||||
},
|
||||
// we assumed that the service is in the same namespace as the trait
|
||||
NamespaceSelector: monitoring.NamespaceSelector{
|
||||
MatchNames: []string{metricsTrait.Namespace},
|
||||
},
|
||||
Endpoints: []monitoring.Endpoint{
|
||||
{
|
||||
TargetPort: &metricsTrait.Spec.ScrapeService.TargetPort,
|
||||
Path: metricsTrait.Spec.ScrapeService.Path,
|
||||
Scheme: metricsTrait.Spec.ScrapeService.Scheme,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MetricsTraitReconciler) 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)
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
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/crossplane/oam-kubernetes-runtime/pkg/oam/util"
|
||||
. "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"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/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: &trueVar,
|
||||
},
|
||||
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 ENV 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(oamServiceLabel))
|
||||
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(oamServiceLabel))
|
||||
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(oamServiceLabel))
|
||||
Expect(createdService.Spec.Selector).Should(Equal(podSelector))
|
||||
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(oamServiceLabel))
|
||||
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))
|
||||
})
|
||||
})
|
||||
129
pkg/controller/metrics/suite_test.go
Normal file
129
pkg/controller/metrics/suite_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
|
||||
oamCore "github.com/crossplane/oam-kubernetes-runtime/apis/core"
|
||||
. "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"
|
||||
|
||||
standardv1alpha1 "github.com/oam-dev/catalog/traits/metricstrait/api/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")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{
|
||||
filepath.Join("..", "config", "crd", "bases"),
|
||||
filepath.Join("..", "hack/crds"), // this has all the required CRDs, a bit hacky
|
||||
},
|
||||
}
|
||||
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())
|
||||
r := MetricsTraitReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("MetricsTrait"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
controllerDone = make(chan struct{}, 1)
|
||||
// +kubebuilder:scaffold:builder
|
||||
go func() {
|
||||
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())
|
||||
|
||||
})
|
||||
25
pkg/utils/json.go
Normal file
25
pkg/utils/json.go
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 utils
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// DumpJSON returns the JSON encoding
|
||||
func DumpJSON(o interface{}) string {
|
||||
j, _ := json.Marshal(o)
|
||||
return string(j)
|
||||
}
|
||||
13
pkg/webhook/metrics/metrics_suite_test.go
Normal file
13
pkg/webhook/metrics/metrics_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestMetrics(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Metrics Suite")
|
||||
}
|
||||
79
pkg/webhook/metrics/metrics_test.go
Normal file
79
pkg/webhook/metrics/metrics_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/v1alpha1"
|
||||
. "github.com/cloud-native-application/rudrx/pkg/webhook/metrics"
|
||||
)
|
||||
|
||||
var _ = Describe("Metrics Admission controller Test", func() {
|
||||
var traitBase v1alpha1.MetricsTrait
|
||||
|
||||
BeforeEach(func() {
|
||||
traitBase = v1alpha1.MetricsTrait{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mutate-hook",
|
||||
},
|
||||
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(false)
|
||||
Default(&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)
|
||||
Default(&trait)
|
||||
Expect(trait).Should(BeEquivalentTo(want))
|
||||
})
|
||||
|
||||
It("Test not fill in enabled field", func() {
|
||||
trait := traitBase
|
||||
trait.Spec.ScrapeService.Enabled = &falseVar
|
||||
want := trait
|
||||
want.Spec.ScrapeService.Format = SupportedFormat
|
||||
want.Spec.ScrapeService.Scheme = SupportedScheme
|
||||
want.Spec.ScrapeService.Path = DefaultMetricsPath
|
||||
want.Spec.ScrapeService.Enabled = &falseVar
|
||||
Default(&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())
|
||||
})
|
||||
})
|
||||
115
pkg/webhook/metrics/mutating_handler.go
Normal file
115
pkg/webhook/metrics/mutating_handler.go
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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/cloud-native-application/rudrx/api/v1alpha1"
|
||||
util "github.com/cloud-native-application/rudrx/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"
|
||||
)
|
||||
|
||||
// CloneSetCreateUpdateHandler handles CloneSet
|
||||
type MetricsTraitMutatingHandler 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 = &MetricsTraitMutatingHandler{}
|
||||
|
||||
// Handle handles admission requests.
|
||||
func (h *MetricsTraitMutatingHandler) 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)
|
||||
}
|
||||
Default(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
|
||||
}
|
||||
|
||||
// Default sets all the default value for the metricsTrait
|
||||
func Default(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 = &MetricsTraitMutatingHandler{}
|
||||
|
||||
// InjectClient injects the client into the MetricsTraitMutatingHandler
|
||||
func (h *MetricsTraitMutatingHandler) InjectClient(c client.Client) error {
|
||||
h.Client = c
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ admission.DecoderInjector = &MetricsTraitMutatingHandler{}
|
||||
|
||||
// InjectDecoder injects the decoder into the MetricsTraitMutatingHandler
|
||||
func (h *MetricsTraitMutatingHandler) InjectDecoder(d *admission.Decoder) error {
|
||||
h.Decoder = d
|
||||
return nil
|
||||
}
|
||||
119
pkg/webhook/metrics/validating_handler.go
Normal file
119
pkg/webhook/metrics/validating_handler.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
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"
|
||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"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/cloud-native-application/rudrx/api/v1alpha1"
|
||||
)
|
||||
|
||||
// MetricsTraitValidatingHandler handles MetricsTrait
|
||||
type MetricsTraitValidatingHandler 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 = &MetricsTraitValidatingHandler{}
|
||||
|
||||
// Handle handles admission requests.
|
||||
func (h *MetricsTraitValidatingHandler) 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)
|
||||
}
|
||||
|
||||
switch req.AdmissionRequest.Operation {
|
||||
case admissionv1beta1.Create:
|
||||
if allErrs := ValidateCreate(obj); len(allErrs) > 0 {
|
||||
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 {
|
||||
return admission.Errored(http.StatusUnprocessableEntity, allErrs.ToAggregate())
|
||||
}
|
||||
}
|
||||
|
||||
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 := apivalidation.ValidateObjectMeta(&r.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||
fldPath := field.NewPath("metadata")
|
||||
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 = &MetricsTraitValidatingHandler{}
|
||||
|
||||
// InjectClient injects the client into the MetricsTraitValidatingHandler
|
||||
func (h *MetricsTraitValidatingHandler) InjectClient(c client.Client) error {
|
||||
h.Client = c
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ admission.DecoderInjector = &MetricsTraitValidatingHandler{}
|
||||
|
||||
// InjectDecoder injects the decoder into the MetricsTraitValidatingHandler
|
||||
func (h *MetricsTraitValidatingHandler) InjectDecoder(d *admission.Decoder) error {
|
||||
h.Decoder = d
|
||||
return nil
|
||||
}
|
||||
20
pkg/webhook/register.go
Normal file
20
pkg/webhook/register.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/webhook/metrics"
|
||||
)
|
||||
|
||||
// +kubebuilder:webhook:verbs=create;update;delete,path=/validate-standard-oam-dev-v1alpha1-metricstrait,mutating=false,failurePolicy=fail,groups=standard.oam.dev,resources=metricstraits,versions=v1alpha1,name=vmetricstrait.kb.io
|
||||
// +kubebuilder:webhook:path=/mutate-standard-oam-dev-v1alpha1-metricstrait,mutating=true,failurePolicy=fail,groups=standard.oam.dev,resources=metricstraits,verbs=create;update,versions=v1alpha1,name=mmetricstrait.kb.io
|
||||
|
||||
// Register will register all the services to the webhook server
|
||||
func Register(mgr manager.Manager) {
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/validate-standard-oam-dev-v1alpha1-metricstrait",
|
||||
&webhook.Admission{Handler: &metrics.MetricsTraitValidatingHandler{}})
|
||||
server.Register("/mutate-standard-oam-dev-v1alpha1-metricstrait",
|
||||
&webhook.Admission{Handler: &metrics.MetricsTraitMutatingHandler{}})
|
||||
}
|
||||
Reference in New Issue
Block a user