diff --git a/.github/workflows/publish-images.yaml b/.github/workflows/publish-images.yaml index 1d0e012f1b..d81713c05f 100644 --- a/.github/workflows/publish-images.yaml +++ b/.github/workflows/publish-images.yaml @@ -24,6 +24,7 @@ jobs: - name: Set env vars for the job run: | grep -v '\#' versions.txt | grep opentelemetry-collector | awk -F= '{print "OTELCOL_VERSION="$2}' >> $GITHUB_ENV + grep -v '\#' versions.txt | grep targetallocator | awk -F= '{print "TARGETALLOCATOR_VERSION="$2}' >> $GITHUB_ENV echo "VERSION_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV echo "VERSION=$(git describe --tags | sed 's/^v//')" >> $GITHUB_ENV @@ -72,5 +73,6 @@ jobs: VERSION=${{ env.VERSION }} VERSION_DATE=${{ env.VERSION_DATE }} OTELCOL_VERSION=${{ env.OTELCOL_VERSION }} + TARGETALLOCATOR_VERSION=${{ env.TARGETALLOCATOR_VERSION }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/Dockerfile b/Dockerfile index bde3f4b0d5..b056cb2c28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,9 +21,10 @@ ARG VERSION_PKG ARG VERSION ARG VERSION_DATE ARG OTELCOL_VERSION +ARG TARGETALLOCATOR_VERSION # Build -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION}" -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION}" -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index 2e3fd47450..20cdca3a67 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ VERSION_DATE ?= $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION_PKG ?= "github.com/open-telemetry/opentelemetry-operator/internal/version" OTELCOL_VERSION ?= "$(shell grep -v '\#' versions.txt | grep opentelemetry-collector | awk -F= '{print $$2}')" OPERATOR_VERSION ?= "$(shell grep -v '\#' versions.txt | grep operator | awk -F= '{print $$2}')" -LD_FLAGS ?= "-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION}" +TARGETALLOCATOR_VERSION ?= "$(shell grep -v '\#' versions.txt | grep targetallocator | awk -F= '{print $$2}')" +LD_FLAGS ?= "-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION}" # Image URL to use all building/pushing image targets IMG_PREFIX ?= quay.io/${USER} @@ -122,7 +123,7 @@ set-test-image-vars: # Build the container image, used only for local dev purposes container: - docker build -t ${IMG} --build-arg VERSION_PKG=${VERSION_PKG} --build-arg VERSION=${VERSION} --build-arg VERSION_DATE=${VERSION_DATE} --build-arg OTELCOL_VERSION=${OTELCOL_VERSION} . + docker build -t ${IMG} --build-arg VERSION_PKG=${VERSION_PKG} --build-arg VERSION=${VERSION} --build-arg VERSION_DATE=${VERSION_DATE} --build-arg OTELCOL_VERSION=${OTELCOL_VERSION} --build-arg TARGETALLOCATOR_VERSION=${TARGETALLOCATOR_VERSION} . # Push the container image, used only for local dev purposes container-push: diff --git a/api/v1alpha1/opentelemetrycollector_types.go b/api/v1alpha1/opentelemetrycollector_types.go index d8528fdb39..63921453a2 100644 --- a/api/v1alpha1/opentelemetrycollector_types.go +++ b/api/v1alpha1/opentelemetrycollector_types.go @@ -41,6 +41,11 @@ type OpenTelemetryCollectorSpec struct { // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true Image string `json:"image,omitempty"` + // TargetAllocator indicates a value which determines whether to spawn a target allocation resource or not. + // +optional + // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true + TargetAllocator OpenTelemetryTargetAllocatorSpec `json:"targetAllocator,omitempty"` + // Mode represents how the collector should be deployed (deployment, daemonset, statefulset or sidecar) // +optional // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true @@ -116,6 +121,17 @@ type OpenTelemetryCollectorStatus struct { Messages []string `json:"messages,omitempty"` } +// OpenTelemetryTargetAllocatorSpec defines the configurations for the Prometheus target allocator. +type OpenTelemetryTargetAllocatorSpec struct { + // Enabled indicates whether to use a target allocation mechanism for Prometheus targets or not. + // +optional + Enabled bool `json:"enabled,omitempty"` + + // Image indicates the container image to use for the OpenTelemetry TargetAllocator. + // +optional + Image string `json:"image,omitempty"` +} + // +kubebuilder:object:root=true // +kubebuilder:resource:shortName=otelcol;otelcols // +kubebuilder:subresource:status diff --git a/api/v1alpha1/opentelemetrycollector_webhook.go b/api/v1alpha1/opentelemetrycollector_webhook.go index d0ace6187b..77499d11da 100644 --- a/api/v1alpha1/opentelemetrycollector_webhook.go +++ b/api/v1alpha1/opentelemetrycollector_webhook.go @@ -21,6 +21,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" + + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) // log is for logging in this package. @@ -91,5 +93,18 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error { return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the attribute 'tolerations'", r.Spec.Mode) } + // validate target allocation + if r.Spec.TargetAllocator.Enabled && r.Spec.Mode != ModeStatefulSet { + return fmt.Errorf("the OpenTelemetry Collector mode is set to %s, which does not support the target allocation deployment", r.Spec.Mode) + } + + // validate Prometheus config for target allocation + if r.Spec.TargetAllocator.Enabled { + _, err := ta.ConfigToPromConfig(r.Spec.Config) + if err != nil { + return fmt.Errorf("the OpenTelemetry Spec Prometheus configuration is incorrect, %s", err) + } + } + return nil } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3a2e11fa66..9a0fffd24f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -97,6 +97,7 @@ func (in *OpenTelemetryCollectorSpec) DeepCopyInto(out *OpenTelemetryCollectorSp *out = new(int32) **out = **in } + out.TargetAllocator = in.TargetAllocator if in.SecurityContext != nil { in, out := &in.SecurityContext, &out.SecurityContext *out = new(v1.SecurityContext) @@ -176,3 +177,18 @@ func (in *OpenTelemetryCollectorStatus) DeepCopy() *OpenTelemetryCollectorStatus in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenTelemetryTargetAllocatorSpec) DeepCopyInto(out *OpenTelemetryTargetAllocatorSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetryTargetAllocatorSpec. +func (in *OpenTelemetryTargetAllocatorSpec) DeepCopy() *OpenTelemetryTargetAllocatorSpec { + if in == nil { + return nil + } + out := new(OpenTelemetryTargetAllocatorSpec) + in.DeepCopyInto(out) + return out +} diff --git a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml index 817fa2573d..56ac10808b 100644 --- a/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -442,6 +442,19 @@ spec: description: ServiceAccount indicates the name of an existing service account to use with this instance. type: string + targetAllocator: + description: TargetAllocator indicates a value which determines whether + to spawn a target allocation resource or not. + properties: + enabled: + description: Enabled indicates whether to use a target allocation + mechanism for Prometheus targets or not. + type: boolean + image: + description: Image indicates the container image to use for the + OpenTelemetry TargetAllocator. + type: string + type: object tolerations: description: Toleration to schedule OpenTelemetry Collector pods. This is only relevant to daemonsets, statefulsets and deployments diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index 13ef39a70b..462f10a997 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -430,6 +430,19 @@ spec: description: ServiceAccount indicates the name of an existing service account to use with this instance. type: string + targetAllocator: + description: TargetAllocator indicates a value which determines whether + to spawn a target allocation resource or not. + properties: + enabled: + description: Enabled indicates whether to use a target allocation + mechanism for Prometheus targets or not. + type: boolean + image: + description: Image indicates the container image to use for the + OpenTelemetry TargetAllocator. + type: string + type: object tolerations: description: Toleration to schedule OpenTelemetry Collector pods. This is only relevant to daemonsets, statefulsets and deployments diff --git a/internal/config/main.go b/internal/config/main.go index 52b9e49586..d5f2be2d4e 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -29,8 +29,9 @@ import ( ) const ( - defaultAutoDetectFrequency = 5 * time.Second - defaultCollectorConfigMapEntry = "collector.yaml" + defaultAutoDetectFrequency = 5 * time.Second + defaultCollectorConfigMapEntry = "collector.yaml" + defaultTargetAllocatorConfigMapEntry = "targetallocator.yaml" ) // Config holds the static configuration for this operator. @@ -44,21 +45,24 @@ type Config struct { onChange []func() error // config state - collectorImage string - collectorConfigMapEntry string - platform platform.Platform - version version.Version + collectorImage string + collectorConfigMapEntry string + targetAllocatorImage string + targetAllocatorConfigMapEntry string + platform platform.Platform + version version.Version } // New constructs a new configuration based on the given options. func New(opts ...Option) Config { // initialize with the default values o := options{ - autoDetectFrequency: defaultAutoDetectFrequency, - collectorConfigMapEntry: defaultCollectorConfigMapEntry, - logger: logf.Log.WithName("config"), - platform: platform.Unknown, - version: version.Get(), + autoDetectFrequency: defaultAutoDetectFrequency, + collectorConfigMapEntry: defaultCollectorConfigMapEntry, + targetAllocatorConfigMapEntry: defaultTargetAllocatorConfigMapEntry, + logger: logf.Log.WithName("config"), + platform: platform.Unknown, + version: version.Get(), } for _, opt := range opts { opt(&o) @@ -70,15 +74,21 @@ func New(opts ...Option) Config { o.collectorImage = fmt.Sprintf("otel/opentelemetry-collector:%s", o.version.OpenTelemetryCollector) } + if len(o.targetAllocatorImage) == 0 { + o.targetAllocatorImage = fmt.Sprintf("quay.io/opentelemetry/target-allocator:%s", o.version.TargetAllocator) + } + return Config{ - autoDetect: o.autoDetect, - autoDetectFrequency: o.autoDetectFrequency, - collectorImage: o.collectorImage, - collectorConfigMapEntry: o.collectorConfigMapEntry, - logger: o.logger, - onChange: o.onChange, - platform: o.platform, - version: o.version, + autoDetect: o.autoDetect, + autoDetectFrequency: o.autoDetectFrequency, + collectorImage: o.collectorImage, + collectorConfigMapEntry: o.collectorConfigMapEntry, + targetAllocatorImage: o.targetAllocatorImage, + targetAllocatorConfigMapEntry: o.targetAllocatorConfigMapEntry, + logger: o.logger, + onChange: o.onChange, + platform: o.platform, + version: o.version, } } @@ -155,6 +165,16 @@ func (c *Config) CollectorConfigMapEntry() string { return c.collectorConfigMapEntry } +// TargetAllocatorImage represents the flag to override the OpenTelemetry TargetAllocator container image. +func (c *Config) TargetAllocatorImage() string { + return c.targetAllocatorImage +} + +// TargetAllocatorConfigMapEntry represents the configuration file name for the TargetAllocator. Immutable. +func (c *Config) TargetAllocatorConfigMapEntry() string { + return c.targetAllocatorConfigMapEntry +} + // Platform represents the type of the platform this operator is running. func (c *Config) Platform() platform.Platform { return c.platform diff --git a/internal/config/options.go b/internal/config/options.go index 41d39bfe39..b8a2821457 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -28,14 +28,16 @@ import ( type Option func(c *options) type options struct { - autoDetect autodetect.AutoDetect - autoDetectFrequency time.Duration - collectorImage string - collectorConfigMapEntry string - logger logr.Logger - onChange []func() error - platform platform.Platform - version version.Version + autoDetect autodetect.AutoDetect + autoDetectFrequency time.Duration + targetAllocatorImage string + collectorImage string + collectorConfigMapEntry string + targetAllocatorConfigMapEntry string + logger logr.Logger + onChange []func() error + platform platform.Platform + version version.Version } func WithAutoDetect(a autodetect.AutoDetect) Option { @@ -48,6 +50,12 @@ func WithAutoDetectFrequency(t time.Duration) Option { o.autoDetectFrequency = t } } +func WithTargetAllocatorImage(s string) Option { + return func(o *options) { + o.targetAllocatorImage = s + } +} + func WithCollectorImage(s string) Option { return func(o *options) { o.collectorImage = s @@ -58,6 +66,11 @@ func WithCollectorConfigMapEntry(s string) Option { o.collectorConfigMapEntry = s } } +func WithTargetAllocatorConfigMapEntry(s string) Option { + return func(o *options) { + o.targetAllocatorConfigMapEntry = s + } +} func WithLogger(logger logr.Logger) Option { return func(o *options) { o.logger = logger diff --git a/internal/version/main.go b/internal/version/main.go index c0080a4b2a..9966a3f6a1 100644 --- a/internal/version/main.go +++ b/internal/version/main.go @@ -21,9 +21,10 @@ import ( ) var ( - version string - buildDate string - otelCol string + version string + buildDate string + otelCol string + targetAllocator string ) // Version holds this Operator's version as well as the version of some of the components it uses. @@ -32,6 +33,7 @@ type Version struct { BuildDate string `json:"build-date"` OpenTelemetryCollector string `json:"opentelemetry-collector-version"` Go string `json:"go-version"` + TargetAllocator string `json:"target-allocator-version"` } // Get returns the Version object with the relevant information. @@ -41,16 +43,18 @@ func Get() Version { BuildDate: buildDate, OpenTelemetryCollector: OpenTelemetryCollector(), Go: runtime.Version(), + TargetAllocator: TargetAllocator(), } } func (v Version) String() string { return fmt.Sprintf( - "Version(Operator='%v', BuildDate='%v', OpenTelemetryCollector='%v', Go='%v')", + "Version(Operator='%v', BuildDate='%v', OpenTelemetryCollector='%v', Go='%v', TargetAllocator='%v')", v.Operator, v.BuildDate, v.OpenTelemetryCollector, v.Go, + v.TargetAllocator, ) } @@ -64,3 +68,14 @@ func OpenTelemetryCollector() string { // fallback value, useful for tests return "0.0.0" } + +// TargetAllocator returns the default TargetAllocator to use when no versions are specified via CLI or configuration. +func TargetAllocator() string { + if len(targetAllocator) > 0 { + // this should always be set, as it's specified during the build + return targetAllocator + } + + // fallback value, useful for tests + return "0.0.0" +} diff --git a/internal/version/main_test.go b/internal/version/main_test.go index ec9a028605..7855991957 100644 --- a/internal/version/main_test.go +++ b/internal/version/main_test.go @@ -34,3 +34,18 @@ func TestVersionFromBuild(t *testing.T) { assert.Equal(t, otelCol, OpenTelemetryCollector()) assert.Contains(t, Get().String(), otelCol) } + +func TestTargetAllocatorFallbackVersion(t *testing.T) { + assert.Equal(t, "0.0.0", TargetAllocator()) +} + +func TestTargetAllocatorVersionFromBuild(t *testing.T) { + // prepare + targetAllocator = "0.0.2" // set during the build + defer func() { + targetAllocator = "" + }() + + assert.Equal(t, targetAllocator, TargetAllocator()) + assert.Contains(t, Get().String(), targetAllocator) +} diff --git a/main.go b/main.go index 0e2aa7ee9b..194dc00438 100644 --- a/main.go +++ b/main.go @@ -76,6 +76,7 @@ func main() { logger.Info("Starting the OpenTelemetry Operator", "opentelemetry-operator", v.Operator, "opentelemetry-collector", v.OpenTelemetryCollector, + "opentelemetry-targetallocator", v.TargetAllocator, "build-date", v.BuildDate, "go-version", v.Go, "go-arch", runtime.GOARCH, diff --git a/pkg/collector/adapters/config_to_ports.go b/pkg/collector/adapters/config_to_ports.go index 70f12a388f..a3fc2822cb 100644 --- a/pkg/collector/adapters/config_to_ports.go +++ b/pkg/collector/adapters/config_to_ports.go @@ -73,7 +73,7 @@ func ConfigToReceiverPorts(logger logr.Logger, config map[interface{}]interface{ // should we break the process and return an error, or just ignore this faulty parser // and let the other parsers add their ports to the service? right now, the best // option seems to be to log the failures and move on, instead of failing them all - logger.Error(err, "parser for '%s' has returned an error: %v", rcvrName, err) + logger.Error(err, "parser for '%s' has returned an error: %w", rcvrName, err) continue } diff --git a/pkg/collector/container_test.go b/pkg/collector/container_test.go index 5ada9ec80e..82c70aab95 100644 --- a/pkg/collector/container_test.go +++ b/pkg/collector/container_test.go @@ -17,12 +17,11 @@ package collector_test import ( "testing" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" logf "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/stretchr/testify/assert" - "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" . "github.com/open-telemetry/opentelemetry-operator/pkg/collector" diff --git a/pkg/collector/deployment_test.go b/pkg/collector/deployment_test.go index dea1c308c4..1cfe13594e 100644 --- a/pkg/collector/deployment_test.go +++ b/pkg/collector/deployment_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index 82d57b5393..c628165dbe 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -19,6 +19,7 @@ import ( "fmt" "reflect" + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +29,8 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete @@ -38,14 +41,22 @@ func ConfigMaps(ctx context.Context, params Params) error { desiredConfigMap(ctx, params), } + if params.Instance.Spec.TargetAllocator.Enabled { + cm, err := desiredTAConfigMap(params) + if err != nil { + return fmt.Errorf("failed to parse config: %w", err) + } + desired = append(desired, cm) + } + // first, handle the create/update parts if err := expectedConfigMaps(ctx, params, desired, true); err != nil { - return fmt.Errorf("failed to reconcile the expected configmaps: %v", err) + return fmt.Errorf("failed to reconcile the expected configmaps: %w", err) } // then, delete the extra objects if err := deleteConfigMaps(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the configmaps to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the configmaps to be deleted: %w", err) } return nil @@ -69,6 +80,41 @@ func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap { } } +func desiredTAConfigMap(params Params) (corev1.ConfigMap, error) { + name := naming.TAConfigMap(params.Instance) + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = name + + promConfig, err := ta.ConfigToPromConfig(params.Instance.Spec.Config) + if err != nil { + return corev1.ConfigMap{}, err + } + + taConfig := make(map[interface{}]interface{}) + taConfig["label_selector"] = map[string]string{ + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.Instance.Namespace, params.Instance.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-collector", + } + taConfig["config"] = promConfig + taConfigYAML, err := yaml.Marshal(taConfig) + if err != nil { + return corev1.ConfigMap{}, err + } + + return corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Instance.Namespace, + Labels: labels, + Annotations: params.Instance.Annotations, + }, + Data: map[string]string{ + "targetallocator.yaml": string(taConfigYAML), + }, + }, nil +} + func expectedConfigMaps(ctx context.Context, params Params, expected []corev1.ConfigMap, retry bool) error { for _, obj := range expected { desired := obj diff --git a/pkg/collector/reconcile/configmap_test.go b/pkg/collector/reconcile/configmap_test.go index 7eaee37fc5..01747b0828 100644 --- a/pkg/collector/reconcile/configmap_test.go +++ b/pkg/collector/reconcile/configmap_test.go @@ -19,44 +19,52 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" v1 "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/client-go/tools/record" "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/internal/config" + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" ) func TestDesiredConfigMap(t *testing.T) { - t.Run("should return expected config map", func(t *testing.T) { - expectedLables := map[string]string{ - "app.kubernetes.io/managed-by": "opentelemetry-operator", - "app.kubernetes.io/instance": "default.test", - "app.kubernetes.io/part-of": "opentelemetry", - "app.kubernetes.io/component": "opentelemetry-collector", - "app.kubernetes.io/name": "test-collector", - } + expectedLables := map[string]string{ + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/part-of": "opentelemetry", + } - expectedData := map[string]string{ - "collector.yaml": ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] + t.Run("should return expected collector config map", func(t *testing.T) { + expectedLables["app.kubernetes.io/component"] = "opentelemetry-collector" + expectedLables["app.kubernetes.io/name"] = "test-collector" -`, + expectedData := map[string]string{ + "collector.yaml": `processors: +receivers: + jaeger: + protocols: + grpc: + prometheus: + config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888', '0.0.0.0:9999' ] + +exporters: + logging: + +service: + pipelines: + metrics: + receivers: [prometheus] + processors: [] + exporters: [logging]`, } actual := desiredConfigMap(context.Background(), params()) @@ -67,20 +75,56 @@ func TestDesiredConfigMap(t *testing.T) { }) + t.Run("should return expected target allocator config map", func(t *testing.T) { + expectedLables["app.kubernetes.io/component"] = "opentelemetry-targetallocator" + expectedLables["app.kubernetes.io/name"] = "test-targetallocator" + + expectedData := map[string]string{ + "targetallocator.yaml": `config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: + - 0.0.0.0:8888 + - 0.0.0.0:9999 +label_selector: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/instance: default.test + app.kubernetes.io/managed-by: opentelemetry-operator +`, + } + + actual, err := desiredTAConfigMap(params()) + assert.NoError(t, err) + + assert.Equal(t, "test-targetallocator", actual.Name) + assert.Equal(t, expectedLables, actual.Labels) + assert.Equal(t, expectedData, actual.Data) + + }) + } func TestExpectedConfigMap(t *testing.T) { - t.Run("should create config map", func(t *testing.T) { - err := expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params())}, true) + t.Run("should create collector and target allocator config maps", func(t *testing.T) { + configMap, err := desiredTAConfigMap(params()) + assert.NoError(t, err) + err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{desiredConfigMap(context.Background(), params()), configMap}, true) assert.NoError(t, err) exists, err := populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-collector"}) assert.NoError(t, err) assert.True(t, exists) + + exists, err = populateObjectIfExists(t, &v1.ConfigMap{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) }) - t.Run("should update config map", func(t *testing.T) { + t.Run("should update collector config map", func(t *testing.T) { param := Params{ Config: config.New(), @@ -115,6 +159,71 @@ func TestExpectedConfigMap(t *testing.T) { assert.Equal(t, params().Instance.Spec.Config, actual.Data["collector.yaml"]) }) + t.Run("should update target allocator config map", func(t *testing.T) { + + param := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ + Enabled: true, + }, + Config: "", + }, + }, + Scheme: testScheme, + Log: logger, + } + cm, err := desiredTAConfigMap(param) + assert.EqualError(t, err, "no receivers available as part of the configuration") + createObjectIfNotExists(t, "test-targetallocator", &cm) + + configMap, err := desiredTAConfigMap(params()) + assert.NoError(t, err) + err = expectedConfigMaps(context.Background(), params(), []v1.ConfigMap{configMap}, true) + assert.NoError(t, err) + + actual := v1.ConfigMap{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, instanceUID, actual.OwnerReferences[0].UID) + + parmConfig, err := ta.ConfigToPromConfig(params().Instance.Spec.Config) + assert.NoError(t, err) + + taConfig := make(map[interface{}]interface{}) + taConfig["label_selector"] = map[string]string{ + "app.kubernetes.io/instance": "default.test", + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-collector", + } + taConfig["config"] = parmConfig + taConfigYAML, _ := yaml.Marshal(taConfig) + + assert.Equal(t, string(taConfigYAML), actual.Data["targetallocator.yaml"]) + }) + t.Run("should delete config map", func(t *testing.T) { deletecm := v1.ConfigMap{ diff --git a/pkg/collector/reconcile/daemonset.go b/pkg/collector/reconcile/daemonset.go index fa6a1a6f82..8acbfa2229 100644 --- a/pkg/collector/reconcile/daemonset.go +++ b/pkg/collector/reconcile/daemonset.go @@ -38,12 +38,12 @@ func DaemonSets(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedDaemonSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected daemon sets: %v", err) + return fmt.Errorf("failed to reconcile the expected daemon sets: %w", err) } // then, delete the extra objects if err := deleteDaemonSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the daemon sets to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the daemon sets to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/deployment.go b/pkg/collector/reconcile/deployment.go index 41b0f0de65..8176390948 100644 --- a/pkg/collector/reconcile/deployment.go +++ b/pkg/collector/reconcile/deployment.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/open-telemetry/opentelemetry-operator/pkg/collector" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete @@ -36,14 +37,18 @@ func Deployments(ctx context.Context, params Params) error { desired = append(desired, collector.Deployment(params.Config, params.Log, params.Instance)) } + if params.Instance.Spec.TargetAllocator.Enabled { + desired = append(desired, targetallocator.Deployment(params.Config, params.Log, params.Instance)) + } + // first, handle the create/update parts if err := expectedDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected deployments: %v", err) + return fmt.Errorf("failed to reconcile the expected deployments: %w", err) } // then, delete the extra objects if err := deleteDeployments(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the deployments to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the deployments to be deleted: %w", err) } return nil @@ -79,7 +84,11 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D updated.Labels = map[string]string{} } - updated.Spec = desired.Spec + if desired.Labels["app.kubernetes.io/component"] == "opentelemetry-targetallocator" { + updated.Spec.Template.Spec.Containers[0].Image = desired.Spec.Template.Spec.Containers[0].Image + } else { + updated.Spec = desired.Spec + } updated.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences for k, v := range desired.ObjectMeta.Annotations { diff --git a/pkg/collector/reconcile/deployment_test.go b/pkg/collector/reconcile/deployment_test.go index 7295ef05fc..668cb93191 100644 --- a/pkg/collector/reconcile/deployment_test.go +++ b/pkg/collector/reconcile/deployment_test.go @@ -24,14 +24,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/collector" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) func TestExpectedDeployments(t *testing.T) { param := params() expectedDeploy := collector.Deployment(param.Config, logger, param.Instance) + expectedTADeploy := targetallocator.Deployment(param.Config, logger, param.Instance) - t.Run("should create deployment", func(t *testing.T) { + t.Run("should create collector deployment", func(t *testing.T) { err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) assert.NoError(t, err) @@ -41,6 +44,64 @@ func TestExpectedDeployments(t *testing.T) { assert.True(t, exists) }) + + t.Run("should create target allocator deployment", func(t *testing.T) { + err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedTADeploy}) + assert.NoError(t, err) + + exists, err := populateObjectIfExists(t, &v1.Deployment{}, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + + }) + + t.Run("should not create target allocator deployment when targetallocator is not enabled", func(t *testing.T) { + param := Params{ + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Config: ` + receivers: + jaeger: + protocols: + grpc: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] + + `, + }, + }, + Scheme: testScheme, + Log: logger, + } + expected := []v1.Deployment{} + if param.Instance.Spec.TargetAllocator.Enabled { + expected = append(expected, targetallocator.Deployment(param.Config, param.Log, param.Instance)) + } + + assert.Len(t, expected, 0) + }) + t.Run("should update deployment", func(t *testing.T) { createObjectIfNotExists(t, "test-collector", &expectedDeploy) err := expectedDeployments(context.Background(), param, []v1.Deployment{expectedDeploy}) @@ -55,6 +116,51 @@ func TestExpectedDeployments(t *testing.T) { assert.Equal(t, int32(2), *actual.Spec.Replicas) }) + t.Run("should not update target allocator deployment when the container image is not updated", func(t *testing.T) { + ctx := context.Background() + createObjectIfNotExists(t, "test-targetallocator", &expectedTADeploy) + orgUID := expectedTADeploy.OwnerReferences[0].UID + + updatedParam, err := newParams("test/test-img") + assert.NoError(t, err) + updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, param.Instance) + *updatedDeploy.Spec.Replicas = int32(3) + + err = expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) + assert.Equal(t, expectedTADeploy.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) + assert.Equal(t, int32(1), *actual.Spec.Replicas) + }) + + t.Run("should update target allocator deployment when the container image is updated", func(t *testing.T) { + ctx := context.Background() + createObjectIfNotExists(t, "test-targetallocator", &expectedTADeploy) + orgUID := expectedTADeploy.OwnerReferences[0].UID + + updatedParam, err := newParams("test/test-img") + assert.NoError(t, err) + updatedDeploy := targetallocator.Deployment(updatedParam.Config, logger, updatedParam.Instance) + + err = expectedDeployments(ctx, param, []v1.Deployment{updatedDeploy}) + assert.NoError(t, err) + + actual := v1.Deployment{} + exists, err := populateObjectIfExists(t, &actual, types.NamespacedName{Namespace: "default", Name: "test-targetallocator"}) + + assert.NoError(t, err) + assert.True(t, exists) + assert.Equal(t, orgUID, actual.OwnerReferences[0].UID) + assert.NotEqual(t, expectedTADeploy.Spec.Template.Spec.Containers[0].Image, actual.Spec.Template.Spec.Containers[0].Image) + assert.Equal(t, int32(1), *actual.Spec.Replicas) + }) + t.Run("should delete deployment", func(t *testing.T) { labels := map[string]string{ "app.kubernetes.io/instance": "default.test", diff --git a/pkg/collector/reconcile/service.go b/pkg/collector/reconcile/service.go index 4f5f09813f..b1c9239441 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -23,6 +23,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -30,6 +31,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/pkg/collector" "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" + "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator" ) // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete @@ -48,14 +50,18 @@ func Services(ctx context.Context, params Params) error { } } + if params.Instance.Spec.TargetAllocator.Enabled { + desired = append(desired, desiredTAService(params)) + } + // first, handle the create/update parts if err := expectedServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected services: %v", err) + return fmt.Errorf("failed to reconcile the expected services: %w", err) } // then, delete the extra objects if err := deleteServices(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the services to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the services to be deleted: %w", err) } return nil @@ -121,6 +127,30 @@ func desiredService(ctx context.Context, params Params) *corev1.Service { } } +func desiredTAService(params Params) corev1.Service { + labels := targetallocator.Labels(params.Instance) + labels["app.kubernetes.io/name"] = naming.TAService(params.Instance) + + selector := targetallocator.Labels(params.Instance) + selector["app.kubernetes.io/name"] = naming.TargetAllocator(params.Instance) + + return corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TAService(params.Instance), + Namespace: params.Instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Selector: selector, + Ports: []corev1.ServicePort{{ + Name: "targetallocation", + Port: 80, + TargetPort: intstr.FromInt(8080), + }}, + }, + } +} + func headless(ctx context.Context, params Params) *corev1.Service { h := desiredService(ctx, params) if h == nil { diff --git a/pkg/collector/reconcile/serviceaccount.go b/pkg/collector/reconcile/serviceaccount.go index 9bdbaf5448..ecf140b3dc 100644 --- a/pkg/collector/reconcile/serviceaccount.go +++ b/pkg/collector/reconcile/serviceaccount.go @@ -39,12 +39,12 @@ func ServiceAccounts(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedServiceAccounts(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected service accounts: %v", err) + return fmt.Errorf("failed to reconcile the expected service accounts: %w", err) } // then, delete the extra objects if err := deleteServiceAccounts(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the service accounts to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the service accounts to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/statefulset.go b/pkg/collector/reconcile/statefulset.go index 0468586986..e1ab648b11 100644 --- a/pkg/collector/reconcile/statefulset.go +++ b/pkg/collector/reconcile/statefulset.go @@ -39,12 +39,12 @@ func StatefulSets(ctx context.Context, params Params) error { // first, handle the create/update parts if err := expectedStatefulSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the expected stateful sets: %v", err) + return fmt.Errorf("failed to reconcile the expected stateful sets: %w", err) } // then, delete the extra objects if err := deleteStatefulSets(ctx, params, desired); err != nil { - return fmt.Errorf("failed to reconcile the stateful sets to be deleted: %v", err) + return fmt.Errorf("failed to reconcile the stateful sets to be deleted: %w", err) } return nil diff --git a/pkg/collector/reconcile/suite_test.go b/pkg/collector/reconcile/suite_test.go index d2469ad44a..7fbb84c52d 100644 --- a/pkg/collector/reconcile/suite_test.go +++ b/pkg/collector/reconcile/suite_test.go @@ -17,6 +17,7 @@ package reconcile import ( "context" "fmt" + "io/ioutil" "os" "path/filepath" "testing" @@ -82,6 +83,10 @@ func TestMain(m *testing.M) { func params() Params { replicas := int32(2) + configYAML, err := ioutil.ReadFile("test.yaml") + if err != nil { + fmt.Printf("Error getting yaml file: %v", err) + } return Params{ Config: config.New(), Client: k8sClient, @@ -106,24 +111,7 @@ func params() Params { NodePort: 0, }}, Replicas: &replicas, - Config: ` - receivers: - jaeger: - protocols: - grpc: - processors: - - exporters: - logging: - - service: - pipelines: - traces: - receivers: [jaeger] - processors: [] - exporters: [logging] - -`, + Config: string(configYAML), }, }, Scheme: testScheme, @@ -132,6 +120,52 @@ func params() Params { } } +func newParams(containerImage string) (Params, error) { + replicas := int32(1) + configYAML, err := ioutil.ReadFile("test.yaml") + if err != nil { + return Params{}, fmt.Errorf("Error getting yaml file: %w", err) + } + + cfg := config.New() + + return Params{ + Config: cfg, + Client: k8sClient, + Instance: v1alpha1.OpenTelemetryCollector{ + TypeMeta: metav1.TypeMeta{ + Kind: "opentelemetry.io", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: instanceUID, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + Mode: v1alpha1.ModeStatefulSet, + Ports: []v1.ServicePort{{ + Name: "web", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 80, + }, + NodePort: 0, + }}, + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ + Enabled: true, + Image: containerImage, + }, + Replicas: &replicas, + Config: string(configYAML), + }, + }, + Scheme: testScheme, + Log: logger, + }, nil +} + func createObjectIfNotExists(tb testing.TB, name string, object client.Object) { tb.Helper() err := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: name}, object) diff --git a/pkg/collector/reconcile/test.yaml b/pkg/collector/reconcile/test.yaml new file mode 100644 index 0000000000..e88b110245 --- /dev/null +++ b/pkg/collector/reconcile/test.yaml @@ -0,0 +1,22 @@ +processors: +receivers: + jaeger: + protocols: + grpc: + prometheus: + config: + scrape_configs: + job_name: otel-collector + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888', '0.0.0.0:9999' ] + +exporters: + logging: + +service: + pipelines: + metrics: + receivers: [prometheus] + processors: [] + exporters: [logging] \ No newline at end of file diff --git a/pkg/naming/main.go b/pkg/naming/main.go index cf18051a48..49970f4ec7 100644 --- a/pkg/naming/main.go +++ b/pkg/naming/main.go @@ -26,21 +26,41 @@ func ConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } +// TAConfigMap returns the name for the config map used in the TargetAllocator. +func TAConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) +} + // ConfigMapVolume returns the name to use for the config map's volume in the pod. func ConfigMapVolume() string { return "otc-internal" } +// TAConfigMapVolume returns the name to use for the config map's volume in the TargetAllocator pod. +func TAConfigMapVolume() string { + return "ta-internal" +} + // Container returns the name to use for the container in the pod. func Container() string { return "otc-container" } +// TAContainer returns the name to use for the container in the TargetAllocator pod. +func TAContainer() string { + return "ta-container" +} + // Collector builds the collector (deployment/daemonset) name based on the instance. func Collector(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } +// TargetAllocator returns the TargetAllocator deployment resource name. +func TargetAllocator(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) +} + // HeadlessService builds the name for the headless service based on the instance. func HeadlessService(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-headless", Service(otelcol)) @@ -56,6 +76,11 @@ func Service(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) } +// TAService returns the name to use for the TargetAllocator service. +func TAService(otelcol v1alpha1.OpenTelemetryCollector) string { + return fmt.Sprintf("%s-targetallocator", otelcol.Name) +} + // ServiceAccount builds the service account name based on the instance. func ServiceAccount(otelcol v1alpha1.OpenTelemetryCollector) string { return fmt.Sprintf("%s-collector", otelcol.Name) diff --git a/pkg/targetallocator/adapters/config_to_prom_config.go b/pkg/targetallocator/adapters/config_to_prom_config.go new file mode 100644 index 0000000000..b4ba5f4d59 --- /dev/null +++ b/pkg/targetallocator/adapters/config_to_prom_config.go @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry 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 adapters + +import ( + "fmt" + + "github.com/open-telemetry/opentelemetry-operator/pkg/collector/adapters" +) + +func errorNoComponent(component string) error { + return fmt.Errorf("no %s available as part of the configuration", component) +} + +func errorNotAMap(component string) error { + return fmt.Errorf("%s property in the configuration doesn't contain valid %s", component, component) +} + +// ConfigToPromConfig converts the incoming configuration object into a the Prometheus receiver config. +func ConfigToPromConfig(cfg string) (map[interface{}]interface{}, error) { + config, err := adapters.ConfigFromString(cfg) + if err != nil { + return nil, err + } + + receiversProperty, ok := config["receivers"] + if !ok { + return nil, errorNoComponent("receivers") + } + + receivers, ok := receiversProperty.(map[interface{}]interface{}) + if !ok { + return nil, errorNotAMap("receivers") + } + + prometheusProperty, ok := receivers["prometheus"] + if !ok { + return nil, errorNoComponent("prometheus") + } + + prometheus, ok := prometheusProperty.(map[interface{}]interface{}) + if !ok { + return nil, errorNotAMap("prometheus") + } + + prometheusConfigProperty, ok := prometheus["config"] + if !ok { + return nil, errorNoComponent("prometheusConfig") + } + + prometheusConfig, ok := prometheusConfigProperty.(map[interface{}]interface{}) + if !ok { + return nil, errorNotAMap("prometheusConfig") + } + + return prometheusConfig, nil +} diff --git a/pkg/targetallocator/adapters/config_to_prom_config_test.go b/pkg/targetallocator/adapters/config_to_prom_config_test.go new file mode 100644 index 0000000000..ba5cc2978a --- /dev/null +++ b/pkg/targetallocator/adapters/config_to_prom_config_test.go @@ -0,0 +1,78 @@ +// Copyright The OpenTelemetry 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 adapters_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + ta "github.com/open-telemetry/opentelemetry-operator/pkg/targetallocator/adapters" +) + +func TestExtractPromConfigFromConfig(t *testing.T) { + configStr := `receivers: + examplereceiver: + endpoint: "0.0.0.0:12345" + examplereceiver/settings: + endpoint: "0.0.0.0:12346" + prometheus: + config: + scrape_config: + job_name: otel-collector + scrape_interval: 10s + jaeger/custom: + protocols: + thrift_http: + endpoint: 0.0.0.0:15268 +` + expectedData := map[interface{}]interface{}{ + "scrape_config": map[interface{}]interface{}{ + "job_name": "otel-collector", + "scrape_interval": "10s", + }, + } + + // test + promConfig, err := ta.ConfigToPromConfig(configStr) + assert.NoError(t, err) + + // verify + assert.Equal(t, expectedData, promConfig) +} + +func TestExtractPromConfigFromNullConfig(t *testing.T) { + configStr := `receivers: + examplereceiver: + endpoint: "0.0.0.0:12345" + examplereceiver/settings: + endpoint: "0.0.0.0:12346" + prometheus: + config: + jaeger/custom: + protocols: + thrift_http: + endpoint: 0.0.0.0:15268 +` + + // test + promConfig, err := ta.ConfigToPromConfig(configStr) + assert.Equal(t, err, fmt.Errorf("%s property in the configuration doesn't contain valid %s", "prometheusConfig", "prometheusConfig")) + + // verify + assert.True(t, reflect.ValueOf(promConfig).IsNil()) +} diff --git a/pkg/targetallocator/container.go b/pkg/targetallocator/container.go new file mode 100644 index 0000000000..139f9126e6 --- /dev/null +++ b/pkg/targetallocator/container.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// Container builds a container for the given TargetAllocator. +func Container(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) corev1.Container { + image := otelcol.Spec.TargetAllocator.Image + if len(image) == 0 { + image = cfg.TargetAllocatorImage() + } + + volumeMounts := []corev1.VolumeMount{{ + Name: naming.TAConfigMapVolume(), + MountPath: "/conf", + }} + + envVars := []corev1.EnvVar{} + + envVars = append(envVars, corev1.EnvVar{ + Name: "OTELCOL_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }) + + return corev1.Container{ + Name: naming.TAContainer(), + Image: image, + Env: envVars, + VolumeMounts: volumeMounts, + } +} diff --git a/pkg/targetallocator/container_test.go b/pkg/targetallocator/container_test.go new file mode 100644 index 0000000000..70314f3b69 --- /dev/null +++ b/pkg/targetallocator/container_test.go @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +var logger = logf.Log.WithName("unit-tests") + +func TestContainerNewDefault(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{} + cfg := config.New(config.WithTargetAllocatorImage("default-image")) + + // test + c := Container(cfg, logger, otelcol) + + // verify + assert.Equal(t, "default-image", c.Image) +} + +func TestContainerWithImageOverridden(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ + Enabled: true, + Image: "overridden-image", + }, + }, + } + cfg := config.New(config.WithTargetAllocatorImage("default-image")) + + // test + c := Container(cfg, logger, otelcol) + + // verify + assert.Equal(t, "overridden-image", c.Image) +} + +func TestContainerVolumes(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + Spec: v1alpha1.OpenTelemetryCollectorSpec{ + TargetAllocator: v1alpha1.OpenTelemetryTargetAllocatorSpec{ + Enabled: true, + Image: "default-image", + }, + }, + } + cfg := config.New() + + // test + c := Container(cfg, logger, otelcol) + + // verify + assert.Len(t, c.VolumeMounts, 1) + assert.Equal(t, naming.TAConfigMapVolume(), c.VolumeMounts[0].Name) +} diff --git a/pkg/targetallocator/deployment.go b/pkg/targetallocator/deployment.go new file mode 100644 index 0000000000..d05052dae3 --- /dev/null +++ b/pkg/targetallocator/deployment.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + corev1 "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/naming" +) + +// Deployment builds the deployment for the given instance. +func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) appsv1.Deployment { + labels := Labels(otelcol) + labels["app.kubernetes.io/name"] = naming.TargetAllocator(otelcol) + + var replicas int32 = 1 + + return appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.TargetAllocator(otelcol), + Namespace: otelcol.Namespace, + Labels: labels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: otelcol.Annotations, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{Container(cfg, logger, otelcol)}, + Volumes: Volumes(cfg, otelcol), + }, + }, + }, + } +} diff --git a/pkg/targetallocator/deployment_test.go b/pkg/targetallocator/deployment_test.go new file mode 100644 index 0000000000..302c78ac74 --- /dev/null +++ b/pkg/targetallocator/deployment_test.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" +) + +func TestDeploymentNewDefault(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + }, + } + cfg := config.New() + + // test + d := Deployment(cfg, logger, otelcol) + + // verify + assert.Equal(t, "my-instance-targetallocator", d.Name) + assert.Equal(t, "my-instance-targetallocator", d.Labels["app.kubernetes.io/name"]) + + assert.Len(t, d.Spec.Template.Spec.Containers, 1) + + // none of the default annotations should propagate down to the pod + assert.Empty(t, d.Spec.Template.Annotations) + + // the pod selector should match the pod spec's labels + assert.Equal(t, d.Spec.Template.Labels, d.Spec.Selector.MatchLabels) +} diff --git a/pkg/targetallocator/labels.go b/pkg/targetallocator/labels.go new file mode 100644 index 0000000000..f825d633d9 --- /dev/null +++ b/pkg/targetallocator/labels.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "fmt" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" +) + +// Labels return the common labels to all TargetAllocator objects that are part of a managed OpenTelemetryCollector. +func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { + // new map every time, so that we don't touch the instance's label + base := map[string]string{} + if nil != instance.Labels { + for k, v := range instance.Labels { + base[k] = v + } + } + + base["app.kubernetes.io/managed-by"] = "opentelemetry-operator" + base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Namespace, instance.Name) + base["app.kubernetes.io/part-of"] = "opentelemetry" + base["app.kubernetes.io/component"] = "opentelemetry-targetallocator" + + return base +} diff --git a/pkg/targetallocator/labels_test.go b/pkg/targetallocator/labels_test.go new file mode 100644 index 0000000000..83accd55fb --- /dev/null +++ b/pkg/targetallocator/labels_test.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" +) + +func TestLabelsCommonSet(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + Namespace: "my-ns", + }, + } + + // test + labels := Labels(otelcol) + assert.Equal(t, "opentelemetry-operator", labels["app.kubernetes.io/managed-by"]) + assert.Equal(t, "my-ns.my-instance", labels["app.kubernetes.io/instance"]) + assert.Equal(t, "opentelemetry", labels["app.kubernetes.io/part-of"]) + assert.Equal(t, "opentelemetry-targetallocator", labels["app.kubernetes.io/component"]) +} + +func TestLabelsPropagateDown(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"myapp": "mycomponent"}, + }, + } + + // test + labels := Labels(otelcol) + + // verify + assert.Len(t, labels, 5) + assert.Equal(t, "mycomponent", labels["myapp"]) +} diff --git a/pkg/targetallocator/volume.go b/pkg/targetallocator/volume.go new file mode 100644 index 0000000000..33f901ff6e --- /dev/null +++ b/pkg/targetallocator/volume.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +// Volumes builds the volumes for the given instance, including the config map volume. +func Volumes(cfg config.Config, otelcol v1alpha1.OpenTelemetryCollector) []corev1.Volume { + volumes := []corev1.Volume{{ + Name: naming.TAConfigMapVolume(), + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: naming.TAConfigMap(otelcol)}, + Items: []corev1.KeyToPath{ + { + Key: cfg.TargetAllocatorConfigMapEntry(), + Path: cfg.TargetAllocatorConfigMapEntry(), + }}, + }, + }, + }} + + return volumes +} diff --git a/pkg/targetallocator/volume_test.go b/pkg/targetallocator/volume_test.go new file mode 100644 index 0000000000..11b0da1f2b --- /dev/null +++ b/pkg/targetallocator/volume_test.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry 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 targetallocator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/open-telemetry/opentelemetry-operator/api/v1alpha1" + "github.com/open-telemetry/opentelemetry-operator/internal/config" + "github.com/open-telemetry/opentelemetry-operator/pkg/naming" +) + +func TestVolumeNewDefault(t *testing.T) { + // prepare + otelcol := v1alpha1.OpenTelemetryCollector{} + cfg := config.New() + + // test + volumes := Volumes(cfg, otelcol) + + // verify + assert.Len(t, volumes, 1) + + //check if the number of elements in the volume source items list is 1 + assert.Len(t, volumes[0].VolumeSource.ConfigMap.Items, 1) + + // check that it's the ta-internal volume, with the config map + assert.Equal(t, naming.TAConfigMapVolume(), volumes[0].Name) +} diff --git a/tests/e2e/smoke-targetallocator/00-install.yaml b/tests/e2e/smoke-targetallocator/00-install.yaml new file mode 100644 index 0000000000..9e5058d82b --- /dev/null +++ b/tests/e2e/smoke-targetallocator/00-install.yaml @@ -0,0 +1,8 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pod-view +rules: +- apiGroups: [""] + resources: [ "pods" ] + verbs: [ "get", "list", "watch"] diff --git a/tests/e2e/smoke-targetallocator/01-install.yaml b/tests/e2e/smoke-targetallocator/01-install.yaml new file mode 100644 index 0000000000..d0ccd90869 --- /dev/null +++ b/tests/e2e/smoke-targetallocator/01-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl -n $NAMESPACE create rolebinding default-view-$NAMESPACE --role=pod-view --serviceaccount=$NAMESPACE:default \ No newline at end of file diff --git a/tests/e2e/smoke-targetallocator/02-assert.yaml b/tests/e2e/smoke-targetallocator/02-assert.yaml new file mode 100644 index 0000000000..20e774ce99 --- /dev/null +++ b/tests/e2e/smoke-targetallocator/02-assert.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: stateful-collector +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stateful-targetallocator +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stateful-targetallocator + \ No newline at end of file diff --git a/tests/e2e/smoke-targetallocator/02-install.yaml b/tests/e2e/smoke-targetallocator/02-install.yaml new file mode 100644 index 0000000000..36eb9c9b1c --- /dev/null +++ b/tests/e2e/smoke-targetallocator/02-install.yaml @@ -0,0 +1,33 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + mode: statefulset + targetAllocator: + enabled: true + config: | + receivers: + jaeger: + protocols: + grpc: + + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + + processors: + + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] diff --git a/tests/e2e/targetallocator-features/00-install.yaml b/tests/e2e/targetallocator-features/00-install.yaml new file mode 100644 index 0000000000..aeffa74b71 --- /dev/null +++ b/tests/e2e/targetallocator-features/00-install.yaml @@ -0,0 +1,8 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pod-view +rules: +- apiGroups: [""] + resources: [ "pods" ] + verbs: [ "get", "list", "watch"] \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/01-install.yaml b/tests/e2e/targetallocator-features/01-install.yaml new file mode 100644 index 0000000000..d0ccd90869 --- /dev/null +++ b/tests/e2e/targetallocator-features/01-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl -n $NAMESPACE create rolebinding default-view-$NAMESPACE --role=pod-view --serviceaccount=$NAMESPACE:default \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/02-assert.yaml b/tests/e2e/targetallocator-features/02-assert.yaml new file mode 100644 index 0000000000..cca13596e9 --- /dev/null +++ b/tests/e2e/targetallocator-features/02-assert.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: stateful-collector +spec: + podManagementPolicy: Parallel + template: + spec: + containers: + - args: + - --config=/conf/collector.yaml + name: otc-container + volumeMounts: + - mountPath: /conf + name: otc-internal + - mountPath: /usr/share/testvolume + name: testvolume + volumes: + - configMap: + items: + - key: collector.yaml + path: collector.yaml + name: stateful-collector + name: otc-internal + - emptyDir: {} + name: testvolume + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: testvolume + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + volumeMode: Filesystem +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stateful-targetallocator +spec: + template: + spec: + containers: + - name: ta-container + env: + - name: OTELCOL_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - mountPath: /conf + name: ta-internal + volumes: + - configMap: + items: + - key: targetallocator.yaml + path: targetallocator.yaml + name: stateful-targetallocator + name: ta-internal +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stateful-targetallocator \ No newline at end of file diff --git a/tests/e2e/targetallocator-features/02-install.yaml b/tests/e2e/targetallocator-features/02-install.yaml new file mode 100644 index 0000000000..fc30d5d3e6 --- /dev/null +++ b/tests/e2e/targetallocator-features/02-install.yaml @@ -0,0 +1,46 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + mode: statefulset + volumes: + - name: testvolume + volumeMounts: + - name: testvolume + mountPath: /usr/share/testvolume + volumeClaimTemplates: + - metadata: + name: testvolume + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi + targetAllocator: + enabled: true + config: | + receivers: + jaeger: + protocols: + grpc: + + # Collect own metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: [ '0.0.0.0:8888' ] + + processors: + + exporters: + logging: + service: + pipelines: + traces: + receivers: [jaeger] + processors: [] + exporters: [logging] diff --git a/versions.txt b/versions.txt index 0ed4e12bad..efc8a1110c 100644 --- a/versions.txt +++ b/versions.txt @@ -6,3 +6,6 @@ opentelemetry-collector=0.31.0 # Represents the current release of the OpenTelemetry Operator. operator=0.31.0 + +# Represents the current release of the Target Allocator. +targetallocator=0.1.0