diff --git a/.chloggen/service-extension.yaml b/.chloggen/service-extension.yaml new file mode 100755 index 0000000000..d182754f46 --- /dev/null +++ b/.chloggen/service-extension.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: collector + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: support for creating a service for extensions when ports are specified. + +# One or more tracking issues related to the change +issues: [3460] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/apis/v1beta1/config.go b/apis/v1beta1/config.go index d38e6be70a..5cb9150513 100644 --- a/apis/v1beta1/config.go +++ b/apis/v1beta1/config.go @@ -327,10 +327,14 @@ func (c *Config) GetExtensionPorts(logger logr.Logger) ([]corev1.ServicePort, er return c.getPortsForComponentKinds(logger, KindExtension) } -func (c *Config) GetAllPorts(logger logr.Logger) ([]corev1.ServicePort, error) { +func (c *Config) GetReceiverAndExporterPorts(logger logr.Logger) ([]corev1.ServicePort, error) { return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter) } +func (c *Config) GetAllPorts(logger logr.Logger) ([]corev1.ServicePort, error) { + return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter, KindExtension) +} + func (c *Config) GetEnvironmentVariables(logger logr.Logger) ([]corev1.EnvVar, error) { return c.getEnvironmentVariablesForComponentKinds(logger, KindReceiver) } diff --git a/internal/manifests/collector/service.go b/internal/manifests/collector/service.go index 19559d0c09..7e27eb752c 100644 --- a/internal/manifests/collector/service.go +++ b/internal/manifests/collector/service.go @@ -32,7 +32,6 @@ import ( const ( headlessLabel = "operator.opentelemetry.io/collector-headless-service" monitoringLabel = "operator.opentelemetry.io/collector-monitoring-service" - extensionService = "operator.opentelemetry.io/collector-extension-service" serviceTypeLabel = "operator.opentelemetry.io/collector-service-type" valueExists = "Exists" ) @@ -111,9 +110,8 @@ func MonitoringService(params manifests.Params) (*corev1.Service, error) { } func ExtensionService(params manifests.Params) (*corev1.Service, error) { - name := naming.Service(params.OtelCol.Name) + name := naming.ExtensionService(params.OtelCol.Name) labels := manifestutils.Labels(params.OtelCol.ObjectMeta, name, params.OtelCol.Spec.Image, ComponentOpenTelemetryCollector, []string{}) - labels[extensionService] = valueExists labels[serviceTypeLabel] = ExtensionServiceType.String() annotations, err := manifestutils.Annotations(params.OtelCol, params.Config.AnnotationsFilter()) @@ -154,7 +152,7 @@ func Service(params manifests.Params) (*corev1.Service, error) { return nil, err } - ports, err := params.OtelCol.Spec.Config.GetAllPorts(params.Log) + ports, err := params.OtelCol.Spec.Config.GetReceiverAndExporterPorts(params.Log) if err != nil { return nil, err } diff --git a/internal/manifests/collector/service_test.go b/internal/manifests/collector/service_test.go index d9483c5afb..71f43a03f8 100644 --- a/internal/manifests/collector/service_test.go +++ b/internal/manifests/collector/service_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -26,6 +27,7 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/config" "github.com/open-telemetry/opentelemetry-operator/internal/manifests" "github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils" + "github.com/open-telemetry/opentelemetry-operator/internal/naming" ) func TestExtractPortNumbersAndNames(t *testing.T) { @@ -322,24 +324,31 @@ func TestMonitoringService(t *testing.T) { } func TestExtensionService(t *testing.T) { - t.Run("when the extension has http endpoint", func(t *testing.T) { - params := manifests.Params{ - Config: config.Config{}, - Log: logger, - OtelCol: v1beta1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: v1beta1.OpenTelemetryCollectorSpec{ - Config: v1beta1.Config{ - Service: v1beta1.Service{ - Extensions: []string{"jaeger_query"}, - }, - Extensions: &v1beta1.AnyConfig{ - Object: map[string]interface{}{ - "jaeger_query": map[string]interface{}{ - "http": map[string]interface{}{ - "endpoint": "0.0.0.0:16686", + testCases := []struct { + name string + params manifests.Params + expectedPorts []corev1.ServicePort + }{ + { + name: "when the extension has http endpoint", + params: manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{ + "jaeger_query": map[string]interface{}{ + "http": map[string]interface{}{ + "endpoint": "0.0.0.0:16686", + }, }, }, }, @@ -347,31 +356,36 @@ func TestExtensionService(t *testing.T) { }, }, }, - } - - actual, err := ExtensionService(params) - assert.NotNil(t, actual) - assert.NoError(t, err) - }) - - t.Run("when the extension has grpc endpoint", func(t *testing.T) { - params := manifests.Params{ - Config: config.Config{}, - Log: logger, - OtelCol: v1beta1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", + expectedPorts: []corev1.ServicePort{ + { + Name: "jaeger-query", + Port: 16686, + TargetPort: intstr.IntOrString{ + IntVal: 16686, + }, }, - Spec: v1beta1.OpenTelemetryCollectorSpec{ - Config: v1beta1.Config{ - Service: v1beta1.Service{ - Extensions: []string{"jaeger_query"}, - }, - Extensions: &v1beta1.AnyConfig{ - Object: map[string]interface{}{ - "jaeger_query": map[string]interface{}{ - "grpc": map[string]interface{}{ - "endpoint": "0.0.0.0:16686", + }, + }, + { + name: "when the extension has grpc endpoint", + params: manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{ + "jaeger_query": map[string]interface{}{ + "http": map[string]interface{}{ + "endpoint": "0.0.0.0:16686", + }, }, }, }, @@ -379,34 +393,39 @@ func TestExtensionService(t *testing.T) { }, }, }, - } - - actual, err := ExtensionService(params) - assert.NotNil(t, actual) - assert.NoError(t, err) - }) - - t.Run("when the extension has both http and grpc endpoint", func(t *testing.T) { - params := manifests.Params{ - Config: config.Config{}, - Log: logger, - OtelCol: v1beta1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", + expectedPorts: []corev1.ServicePort{ + { + Name: "jaeger-query", + Port: 16686, + TargetPort: intstr.IntOrString{ + IntVal: 16686, + }, }, - Spec: v1beta1.OpenTelemetryCollectorSpec{ - Config: v1beta1.Config{ - Service: v1beta1.Service{ - Extensions: []string{"jaeger_query"}, - }, - Extensions: &v1beta1.AnyConfig{ - Object: map[string]interface{}{ - "jaeger_query": map[string]interface{}{ - "http": map[string]interface{}{ - "endpoint": "0.0.0.0:16686", - }, - "grpc": map[string]interface{}{ - "endpoint": "0.0.0.0:16686", + }, + }, + { + name: "when the extension has both http and grpc endpoint", + params: manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{ + "jaeger_query": map[string]interface{}{ + "http": map[string]interface{}{ + "endpoint": "0.0.0.0:16686", + }, + "grpc": map[string]interface{}{ + "endpoint": "0.0.0.0:16686", + }, }, }, }, @@ -414,66 +433,94 @@ func TestExtensionService(t *testing.T) { }, }, }, - } - - actual, err := ExtensionService(params) - assert.NotNil(t, actual) - assert.NoError(t, err) - }) - - t.Run("when the extension has no extensions defined", func(t *testing.T) { - params := manifests.Params{ - Config: config.Config{}, - Log: logger, - OtelCol: v1beta1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", + expectedPorts: []corev1.ServicePort{ + { + Name: "jaeger-query", + Port: 16686, + TargetPort: intstr.IntOrString{ + IntVal: 16686, + }, }, - Spec: v1beta1.OpenTelemetryCollectorSpec{ - Config: v1beta1.Config{ - Service: v1beta1.Service{ - Extensions: []string{"jaeger_query"}, - }, - Extensions: &v1beta1.AnyConfig{ - Object: map[string]interface{}{}, + }, + }, + { + name: "when the extension has no extensions defined", + params: manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{}, + }, }, }, }, }, - } - - actual, err := ExtensionService(params) - assert.Nil(t, actual) - assert.NoError(t, err) - }) - - t.Run("when the extension has no endpoint defined", func(t *testing.T) { - params := manifests.Params{ - Config: config.Config{}, - Log: logger, - OtelCol: v1beta1.OpenTelemetryCollector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: v1beta1.OpenTelemetryCollectorSpec{ - Config: v1beta1.Config{ - Service: v1beta1.Service{ - Extensions: []string{"jaeger_query"}, - }, - Extensions: &v1beta1.AnyConfig{ - Object: map[string]interface{}{ - "jaeger_query": map[string]interface{}{}, + expectedPorts: []corev1.ServicePort{}, + }, + { + name: "when the extension has no endpoint defined", + params: manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{ + "jaeger_query": map[string]interface{}{}, + }, }, }, }, }, }, - } + expectedPorts: []corev1.ServicePort{ + { + Name: "jaeger-query", + Port: 16686, + TargetPort: intstr.IntOrString{ + IntVal: 16686, + }, + }, + }, + }, + } - actual, err := ExtensionService(params) - assert.NotNil(t, actual) - assert.NoError(t, err) - }) + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + actual, err := ExtensionService(tc.params) + assert.NoError(t, err) + + if len(tc.expectedPorts) > 0 { + assert.NotNil(t, actual) + assert.Equal(t, actual.Name, naming.ExtensionService(tc.params.OtelCol.Name)) + // ports assertion + assert.Equal(t, len(tc.expectedPorts), len(actual.Spec.Ports)) + assert.Equal(t, tc.expectedPorts[0].Name, actual.Spec.Ports[0].Name) + assert.Equal(t, tc.expectedPorts[0].Port, actual.Spec.Ports[0].Port) + assert.Equal(t, tc.expectedPorts[0].TargetPort.IntVal, actual.Spec.Ports[0].TargetPort.IntVal) + } else { + // no ports, no service + assert.Nil(t, actual) + } + }) + } } func service(name string, ports []v1beta1.PortsSpec) v1.Service { diff --git a/internal/naming/main.go b/internal/naming/main.go index 8642f618c3..149a9f9d5a 100644 --- a/internal/naming/main.go +++ b/internal/naming/main.go @@ -116,6 +116,11 @@ func MonitoringService(otelcol string) string { return DNSName(Truncate("%s-monitoring", 63, Service(otelcol))) } +// ExtensionService builds the name for the extension service based on the instance. +func ExtensionService(otelcol string) string { + return DNSName(Truncate("%s-extension", 63, Service(otelcol))) +} + // Service builds the service name based on the instance. func Service(otelcol string) string { return DNSName(Truncate("%s-collector", 63, otelcol)) diff --git a/tests/e2e/extension/00-assert.yaml b/tests/e2e/extension/00-assert.yaml new file mode 100644 index 0000000000..c62406a1f3 --- /dev/null +++ b/tests/e2e/extension/00-assert.yaml @@ -0,0 +1,140 @@ +apiVersion: v1 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + name: jaeger-inmemory-collector + spec: + template: + spec: + containers: + - ports: + - containerPort: 16686 + name: jaeger-query + protocol: TCP + - containerPort: 8888 + name: metrics + protocol: TCP + - containerPort: 4317 + name: otlp-grpc + protocol: TCP + - containerPort: 4318 + name: otlp-http + protocol: TCP +kind: List +metadata: + resourceVersion: "" +--- +apiVersion: v1 +kind: Service +metadata: + name: jaeger-inmemory-collector +spec: + ports: + - appProtocol: grpc + name: otlp-grpc + port: 4317 + protocol: TCP + targetPort: 4317 + - appProtocol: http + name: otlp-http + port: 4318 + protocol: TCP + targetPort: 4318 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.openshift.io/serving-cert-secret-name: jaeger-inmemory-collector-headless-tls + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: jaeger-inmemory-collector + app.kubernetes.io/part-of: opentelemetry + app.kubernetes.io/version: latest + operator.opentelemetry.io/collector-headless-service: Exists + operator.opentelemetry.io/collector-service-type: headless + name: jaeger-inmemory-collector-headless + ownerReferences: + - apiVersion: opentelemetry.io/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenTelemetryCollector + name: jaeger-inmemory +spec: + clusterIP: None + clusterIPs: + - None + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - appProtocol: grpc + name: otlp-grpc + port: 4317 + protocol: TCP + targetPort: 4317 + - appProtocol: http + name: otlp-http + port: 4318 + protocol: TCP + targetPort: 4318 + selector: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + sessionAffinity: None + type: ClusterIP +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: jaeger-inmemory-collector-monitoring + app.kubernetes.io/part-of: opentelemetry + app.kubernetes.io/version: latest + operator.opentelemetry.io/collector-monitoring-service: Exists + operator.opentelemetry.io/collector-service-type: monitoring + name: jaeger-inmemory-collector-monitoring +spec: + ports: + - name: monitoring + port: 8888 + protocol: TCP + targetPort: 8888 + selector: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + sessionAffinity: None + type: ClusterIP +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: jaeger-inmemory-collector-extension + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + app.kubernetes.io/version: latest + operator.opentelemetry.io/collector-service-type: extension +spec: + selector: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + ports: + - name: jaeger-query + port: 16686 + targetPort: 16686 +status: + loadBalancer: {} diff --git a/tests/e2e/extension/00-install.yaml b/tests/e2e/extension/00-install.yaml new file mode 100644 index 0000000000..43e27fa9b2 --- /dev/null +++ b/tests/e2e/extension/00-install.yaml @@ -0,0 +1,30 @@ +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: jaeger-inmemory +spec: + image: jaegertracing/jaeger:latest + config: + service: + extensions: [jaeger_storage, jaeger_query] + pipelines: + traces: + receivers: [otlp] + exporters: [jaeger_storage_exporter] + extensions: + jaeger_query: + storage: + traces: memstore + jaeger_storage: + backends: + memstore: + memory: + max_traces: 100000 + receivers: + otlp: + protocols: + grpc: + http: + exporters: + jaeger_storage_exporter: + trace_storage: memstore diff --git a/tests/e2e/extension/chainsaw-test.yaml b/tests/e2e/extension/chainsaw-test.yaml new file mode 100644 index 0000000000..488a76359b --- /dev/null +++ b/tests/e2e/extension/chainsaw-test.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: extension-test +spec: + steps: + - name: step-00 + try: + - apply: + file: 00-install.yaml + - assert: + file: 00-assert.yaml