From 5423a71a63eede2b5fdafa7e49a458bfbd482056 Mon Sep 17 00:00:00 2001 From: Vineeth Pothulapati Date: Mon, 7 Jun 2021 17:45:33 +0530 Subject: [PATCH] Tolerations support in otelcol CRD (#302) Signed-off-by: Vineeth Pothulapati --- api/v1alpha1/opentelemetrycollector_types.go | 6 +++ .../opentelemetrycollector_webhook.go | 31 +++++++++----- api/v1alpha1/zz_generated.deepcopy.go | 7 ++++ ...ntelemetry.io_opentelemetrycollectors.yaml | 40 +++++++++++++++++++ pkg/collector/daemonset.go | 1 + pkg/collector/daemonset_test.go | 4 ++ pkg/collector/deployment.go | 1 + pkg/collector/deployment_test.go | 17 +++++++- pkg/collector/statefulset.go | 1 + pkg/collector/statefulset_test.go | 4 +- 10 files changed, 100 insertions(+), 12 deletions(-) diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index 826316c429..48c53b6fc1 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -87,6 +87,12 @@ type OpenTelemetryCollectorSpec struct { // +optional // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true Resources v1.ResourceRequirements `json:"resources,omitempty"` + + // Toleration to schedule OpenTelemetry Collector pods. + // This is only relevant to daemonsets, statefulsets and deployments + // +optional + // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true + Tolerations []v1.Toleration `json:"tolerations,omitempty"` } // OpenTelemetryCollectorStatus defines the observed state of OpenTelemetryCollector. diff --git a/api/v1alpha1/opentelemetrycollector_webhook.go b/api/v1alpha1/opentelemetrycollector_webhook.go index 061dd37455..04b90aa2bd 100644 --- a/api/v1alpha1/opentelemetrycollector_webhook.go +++ b/api/v1alpha1/opentelemetrycollector_webhook.go @@ -15,7 +15,7 @@ package v1alpha1 import ( - "errors" + "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -59,19 +59,13 @@ var _ webhook.Validator = &OpenTelemetryCollector{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *OpenTelemetryCollector) ValidateCreate() error { opentelemetrycollectorlog.Info("validate create", "name", r.Name) - if r.Spec.Mode != ModeStatefulSet && len(r.Spec.VolumeClaimTemplates) > 0 { - return errors.New("Can only specify VolumeClaimTemplates for statefulsets") - } - return nil + return r.validateCRDSpec() } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *OpenTelemetryCollector) ValidateUpdate(old runtime.Object) error { opentelemetrycollectorlog.Info("validate update", "name", r.Name) - if r.Spec.Mode != ModeStatefulSet && len(r.Spec.VolumeClaimTemplates) > 0 { - return errors.New("Can only specify VolumeClaimTemplates for statefulsets") - } - return nil + return r.validateCRDSpec() } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. @@ -79,3 +73,22 @@ func (r *OpenTelemetryCollector) ValidateDelete() error { opentelemetrycollectorlog.Info("validate delete", "name", r.Name) return nil } + +func (r *OpenTelemetryCollector) validateCRDSpec() error { + // validate volumeClaimTemplates + if r.Spec.Mode != ModeStatefulSet && len(r.Spec.VolumeClaimTemplates) > 0 { + return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the attribute 'volumeClaimTemplates'", r.Spec.Mode) + } + + // validate replicas + if (r.Spec.Mode == ModeSidecar || r.Spec.Mode == ModeDaemonSet) && r.Spec.Replicas != nil { + return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the attribute 'replicas'", r.Spec.Mode) + } + + // validate tolerations + if r.Spec.Mode == ModeSidecar && len(r.Spec.Tolerations) > 0 { + return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the attribute 'tolerations'", r.Spec.Mode) + } + + return nil +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3d7374db0a..caee6f0b02 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -133,6 +133,13 @@ func (in *OpenTelemetryCollectorSpec) DeepCopyInto(out *OpenTelemetryCollectorSp } } in.Resources.DeepCopyInto(&out.Resources) + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryCollectorSpec. diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index 2a43e669d1..333106088f 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -280,6 +280,46 @@ spec: description: ServiceAccount indicates the name of an existing service account to use with this instance. type: string + tolerations: + description: Toleration to schedule OpenTelemetry Collector pods. + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeClaimTemplates: description: VolumeClaimTemplates will provide stable storage using PersistentVolumes. Only available when the mode=statefulset. diff --git a/pkg/collector/daemonset.go b/pkg/collector/daemonset.go index 8d6636364c..7c981ee104 100644 --- a/pkg/collector/daemonset.go +++ b/pkg/collector/daemonset.go @@ -52,6 +52,7 @@ func DaemonSet(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelem ServiceAccountName: ServiceAccountName(otelcol), Containers: []corev1.Container{Container(cfg, logger, otelcol)}, Volumes: Volumes(cfg, otelcol), + Tolerations: otelcol.Spec.Tolerations, }, }, }, diff --git a/pkg/collector/daemonset_test.go b/pkg/collector/daemonset_test.go index 59702eece4..79bf9cffdd 100644 --- a/pkg/collector/daemonset_test.go +++ b/pkg/collector/daemonset_test.go @@ -31,6 +31,9 @@ func TestDaemonSetNewDefault(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "my-instance", }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Tolerations: testTolerationValues, + }, } cfg := config.New() @@ -43,6 +46,7 @@ func TestDaemonSetNewDefault(t *testing.T) { assert.Equal(t, "true", d.Annotations["prometheus.io/scrape"]) assert.Equal(t, "8888", d.Annotations["prometheus.io/port"]) assert.Equal(t, "/metrics", d.Annotations["prometheus.io/path"]) + assert.Equal(t, testTolerationValues, d.Spec.Template.Spec.Tolerations) assert.Len(t, d.Spec.Template.Spec.Containers, 1) diff --git a/pkg/collector/deployment.go b/pkg/collector/deployment.go index 0f0f55346c..4d79a2b1cd 100644 --- a/pkg/collector/deployment.go +++ b/pkg/collector/deployment.go @@ -53,6 +53,7 @@ func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTele ServiceAccountName: ServiceAccountName(otelcol), Containers: []corev1.Container{Container(cfg, logger, otelcol)}, Volumes: Volumes(cfg, otelcol), + Tolerations: otelcol.Spec.Tolerations, }, }, }, diff --git a/pkg/collector/deployment_test.go b/pkg/collector/deployment_test.go index 05f54e9720..dea1c308c4 100644 --- a/pkg/collector/deployment_test.go +++ b/pkg/collector/deployment_test.go @@ -17,21 +17,33 @@ package collector_test import ( "testing" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" . "github.com/open-telemetry/opentelemetry-operator/pkg/collector" ) +var testTolerationValues = []v1.Toleration{ + { + Key: "hii", + Value: "greeting", + Effect: "NoSchedule", + }, +} + func TestDeploymentNewDefault(t *testing.T) { // prepare otelcol := v1alpha1.OpenTelemetryCollector{ ObjectMeta: metav1.ObjectMeta{ Name: "my-instance", }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Tolerations: testTolerationValues, + }, } cfg := config.New() @@ -44,6 +56,7 @@ func TestDeploymentNewDefault(t *testing.T) { assert.Equal(t, "true", d.Annotations["prometheus.io/scrape"]) assert.Equal(t, "8888", d.Annotations["prometheus.io/port"]) assert.Equal(t, "/metrics", d.Annotations["prometheus.io/path"]) + assert.Equal(t, testTolerationValues, d.Spec.Template.Spec.Tolerations) assert.Len(t, d.Spec.Template.Spec.Containers, 1) diff --git a/pkg/collector/statefulset.go b/pkg/collector/statefulset.go index ba6bca10f0..925dc66c18 100644 --- a/pkg/collector/statefulset.go +++ b/pkg/collector/statefulset.go @@ -53,6 +53,7 @@ func StatefulSet(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTel ServiceAccountName: ServiceAccountName(otelcol), Containers: []corev1.Container{Container(cfg, logger, otelcol)}, Volumes: Volumes(cfg, otelcol), + Tolerations: otelcol.Spec.Tolerations, }, }, Replicas: otelcol.Spec.Replicas, diff --git a/pkg/collector/statefulset_test.go b/pkg/collector/statefulset_test.go index 88c6206ad9..4262a62026 100644 --- a/pkg/collector/statefulset_test.go +++ b/pkg/collector/statefulset_test.go @@ -35,7 +35,8 @@ func TestStatefulSetNewDefault(t *testing.T) { Name: "my-instance", }, Spec: v1alpha1.OpenTelemetryCollectorSpec{ - Mode: "statefulset", + Mode: "statefulset", + Tolerations: testTolerationValues, }, } cfg := config.New() @@ -49,6 +50,7 @@ func TestStatefulSetNewDefault(t *testing.T) { assert.Equal(t, "true", ss.Annotations["prometheus.io/scrape"]) assert.Equal(t, "8888", ss.Annotations["prometheus.io/port"]) assert.Equal(t, "/metrics", ss.Annotations["prometheus.io/path"]) + assert.Equal(t, testTolerationValues, ss.Spec.Template.Spec.Tolerations) assert.Len(t, ss.Spec.Template.Spec.Containers, 1)