Skip to content

Commit ad0f1f9

Browse files
authored
Replace pulling metrics port with using direct config (open-telemetry#2856)
* checkpoint: with working tests, minimal * chlog * issue * missing change
1 parent fbf82ed commit ad0f1f9

File tree

8 files changed

+193
-91
lines changed

8 files changed

+193
-91
lines changed

.chloggen/2603-part-one.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
5+
component: collector
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Changes metric port logic to use intermediary struct.
9+
10+
# One or more tracking issues related to the change
11+
issues: [2603]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

apis/v1beta1/config.go

+60-17
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ type AnyConfig struct {
3030
}
3131

3232
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
33-
func (in *AnyConfig) DeepCopyInto(out *AnyConfig) {
34-
*out = *in
35-
if in.Object != nil {
36-
in, out := &in.Object, &out.Object
33+
func (c *AnyConfig) DeepCopyInto(out *AnyConfig) {
34+
*out = *c
35+
if c.Object != nil {
36+
in, out := &c.Object, &out.Object
3737
*out = make(map[string]interface{}, len(*in))
3838
for key, val := range *in {
3939
(*out)[key] = val
@@ -42,12 +42,12 @@ func (in *AnyConfig) DeepCopyInto(out *AnyConfig) {
4242
}
4343

4444
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnyConfig.
45-
func (in *AnyConfig) DeepCopy() *AnyConfig {
46-
if in == nil {
45+
func (c *AnyConfig) DeepCopy() *AnyConfig {
46+
if c == nil {
4747
return nil
4848
}
4949
out := new(AnyConfig)
50-
in.DeepCopyInto(out)
50+
c.DeepCopyInto(out)
5151
return out
5252
}
5353

@@ -88,7 +88,7 @@ type Config struct {
8888
}
8989

9090
// Yaml encodes the current object and returns it as a string.
91-
func (c Config) Yaml() (string, error) {
91+
func (c *Config) Yaml() (string, error) {
9292
var buf bytes.Buffer
9393
yamlEncoder := yaml.NewEncoder(&buf)
9494
yamlEncoder.SetIndent(2)
@@ -98,16 +98,8 @@ func (c Config) Yaml() (string, error) {
9898
return buf.String(), nil
9999
}
100100

101-
type Service struct {
102-
Extensions *[]string `json:"extensions,omitempty" yaml:"extensions,omitempty"`
103-
// +kubebuilder:pruning:PreserveUnknownFields
104-
Telemetry *AnyConfig `json:"telemetry,omitempty" yaml:"telemetry,omitempty"`
105-
// +kubebuilder:pruning:PreserveUnknownFields
106-
Pipelines AnyConfig `json:"pipelines" yaml:"pipelines"`
107-
}
108-
109101
// Returns null objects in the config.
110-
func (c Config) nullObjects() []string {
102+
func (c *Config) nullObjects() []string {
111103
var nullKeys []string
112104
if nulls := hasNullValue(c.Receivers.Object); len(nulls) > 0 {
113105
nullKeys = append(nullKeys, addPrefix("receivers.", nulls)...)
@@ -135,6 +127,57 @@ func (c Config) nullObjects() []string {
135127
return nullKeys
136128
}
137129

130+
type Service struct {
131+
Extensions *[]string `json:"extensions,omitempty" yaml:"extensions,omitempty"`
132+
// +kubebuilder:pruning:PreserveUnknownFields
133+
Telemetry *AnyConfig `json:"telemetry,omitempty" yaml:"telemetry,omitempty"`
134+
// +kubebuilder:pruning:PreserveUnknownFields
135+
Pipelines AnyConfig `json:"pipelines" yaml:"pipelines"`
136+
}
137+
138+
// MetricsConfig comes from the collector.
139+
type MetricsConfig struct {
140+
// Level is the level of telemetry metrics, the possible values are:
141+
// - "none" indicates that no telemetry data should be collected;
142+
// - "basic" is the recommended and covers the basics of the service telemetry.
143+
// - "normal" adds some other indicators on top of basic.
144+
// - "detailed" adds dimensions and views to the previous levels.
145+
Level string `json:"level,omitempty" yaml:"level,omitempty"`
146+
147+
// Address is the [address]:port that metrics exposition should be bound to.
148+
Address string `json:"address,omitempty" yaml:"address,omitempty"`
149+
}
150+
151+
// Telemetry is an intermediary type that allows for easy access to the collector's telemetry settings.
152+
type Telemetry struct {
153+
Metrics MetricsConfig `json:"metrics,omitempty" yaml:"metrics,omitempty"`
154+
155+
// Resource specifies user-defined attributes to include with all emitted telemetry.
156+
// Note that some attributes are added automatically (e.g. service.version) even
157+
// if they are not specified here. In order to suppress such attributes the
158+
// attribute must be specified in this map with null YAML value (nil string pointer).
159+
Resource map[string]*string `json:"resource,omitempty" yaml:"resource,omitempty"`
160+
}
161+
162+
// GetTelemetry serves as a helper function to access the fields we care about in the underlying telemetry struct.
163+
// This exists to avoid needing to worry extra fields in the telemetry struct.
164+
func (s *Service) GetTelemetry() *Telemetry {
165+
if s.Telemetry == nil {
166+
return nil
167+
}
168+
// Convert map to JSON bytes
169+
jsonData, err := json.Marshal(s.Telemetry)
170+
if err != nil {
171+
return nil
172+
}
173+
t := &Telemetry{}
174+
// Unmarshal JSON into the provided struct
175+
if err := json.Unmarshal(jsonData, t); err != nil {
176+
return nil
177+
}
178+
return t
179+
}
180+
138181
func hasNullValue(cfg map[string]interface{}) []string {
139182
var nullKeys []string
140183
for k, v := range cfg {

apis/v1beta1/config_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,29 @@ service:
184184

185185
assert.Equal(t, expected, yamlCollector)
186186
}
187+
188+
func TestGetTelemetryFromYAML(t *testing.T) {
189+
collectorYaml, err := os.ReadFile("./testdata/otelcol-demo.yaml")
190+
require.NoError(t, err)
191+
192+
cfg := &Config{}
193+
err = go_yaml.Unmarshal(collectorYaml, cfg)
194+
require.NoError(t, err)
195+
telemetry := &Telemetry{
196+
Metrics: MetricsConfig{
197+
Level: "detailed",
198+
Address: "0.0.0.0:8888",
199+
},
200+
}
201+
assert.Equal(t, telemetry, cfg.Service.GetTelemetry())
202+
}
203+
204+
func TestGetTelemetryFromYAMLIsNil(t *testing.T) {
205+
collectorYaml, err := os.ReadFile("./testdata/otelcol-couchbase.yaml")
206+
require.NoError(t, err)
207+
208+
cfg := &Config{}
209+
err = go_yaml.Unmarshal(collectorYaml, cfg)
210+
require.NoError(t, err)
211+
assert.Nil(t, cfg.Service.GetTelemetry())
212+
}

apis/v1beta1/zz_generated.deepcopy.go

+47
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/manifests/collector/adapters/config_to_ports.go

+5-23
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"strings"
2323

2424
"github.com/go-logr/logr"
25-
"github.com/mitchellh/mapstructure"
2625
corev1 "k8s.io/api/core/v1"
2726

2827
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
@@ -153,29 +152,12 @@ func ConfigToPorts(logger logr.Logger, config map[interface{}]interface{}) ([]v1
153152
}
154153

155154
// ConfigToMetricsPort gets the port number for the metrics endpoint from the collector config if it has been set.
156-
func ConfigToMetricsPort(logger logr.Logger, config map[interface{}]interface{}) (int32, error) {
157-
// we don't need to unmarshal the whole config, just follow the keys down to
158-
// the metrics address.
159-
type metricsCfg struct {
160-
Address string
161-
}
162-
type telemetryCfg struct {
163-
Metrics metricsCfg
164-
}
165-
type serviceCfg struct {
166-
Telemetry telemetryCfg
167-
}
168-
type cfg struct {
169-
Service serviceCfg
170-
}
171-
172-
var cOut cfg
173-
err := mapstructure.Decode(config, &cOut)
174-
if err != nil {
175-
return 0, err
155+
func ConfigToMetricsPort(config v1beta1.Service) (int32, error) {
156+
if config.GetTelemetry() == nil {
157+
// telemetry isn't set, use the default
158+
return 8888, nil
176159
}
177-
178-
_, port, netErr := net.SplitHostPort(cOut.Service.Telemetry.Metrics.Address)
160+
_, port, netErr := net.SplitHostPort(config.GetTelemetry().Metrics.Address)
179161
if netErr != nil && strings.Contains(netErr.Error(), "missing port in address") {
180162
return 8888, nil
181163
} else if netErr != nil {

internal/manifests/collector/adapters/config_to_ports_test.go

+34-35
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"k8s.io/apimachinery/pkg/util/intstr"
2626
logf "sigs.k8s.io/controller-runtime/pkg/log"
2727

28+
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
2829
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/adapters"
2930
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser"
3031
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser/receiver"
@@ -210,34 +211,33 @@ func TestParserFailed(t *testing.T) {
210211
assert.NoError(t, err)
211212
assert.True(t, mockParserCalled)
212213
}
213-
214214
func TestConfigToMetricsPort(t *testing.T) {
215-
t.Run("custom port specified", func(t *testing.T) {
216-
config := map[interface{}]interface{}{
217-
"service": map[interface{}]interface{}{
218-
"telemetry": map[interface{}]interface{}{
219-
"metrics": map[interface{}]interface{}{
220-
"address": "0.0.0.0:9090",
221-
},
222-
},
223-
},
224-
}
225-
226-
port, err := adapters.ConfigToMetricsPort(logger, config)
227-
assert.NoError(t, err)
228-
assert.Equal(t, int32(9090), port)
229-
})
230215

231216
for _, tt := range []struct {
232-
desc string
233-
config map[interface{}]interface{}
217+
desc string
218+
expectedPort int32
219+
config v1beta1.Service
234220
}{
221+
{
222+
"custom port",
223+
9090,
224+
v1beta1.Service{
225+
Telemetry: &v1beta1.AnyConfig{
226+
Object: map[string]interface{}{
227+
"metrics": map[string]interface{}{
228+
"address": "0.0.0.0:9090",
229+
},
230+
},
231+
},
232+
},
233+
},
235234
{
236235
"bad address",
237-
map[interface{}]interface{}{
238-
"service": map[interface{}]interface{}{
239-
"telemetry": map[interface{}]interface{}{
240-
"metrics": map[interface{}]interface{}{
236+
8888,
237+
v1beta1.Service{
238+
Telemetry: &v1beta1.AnyConfig{
239+
Object: map[string]interface{}{
240+
"metrics": map[string]interface{}{
241241
"address": "0.0.0.0",
242242
},
243243
},
@@ -246,10 +246,11 @@ func TestConfigToMetricsPort(t *testing.T) {
246246
},
247247
{
248248
"missing address",
249-
map[interface{}]interface{}{
250-
"service": map[interface{}]interface{}{
251-
"telemetry": map[interface{}]interface{}{
252-
"metrics": map[interface{}]interface{}{
249+
8888,
250+
v1beta1.Service{
251+
Telemetry: &v1beta1.AnyConfig{
252+
Object: map[string]interface{}{
253+
"metrics": map[string]interface{}{
253254
"level": "detailed",
254255
},
255256
},
@@ -258,24 +259,22 @@ func TestConfigToMetricsPort(t *testing.T) {
258259
},
259260
{
260261
"missing metrics",
261-
map[interface{}]interface{}{
262-
"service": map[interface{}]interface{}{
263-
"telemetry": map[interface{}]interface{}{},
264-
},
262+
8888,
263+
v1beta1.Service{
264+
Telemetry: &v1beta1.AnyConfig{},
265265
},
266266
},
267267
{
268268
"missing telemetry",
269-
map[interface{}]interface{}{
270-
"service": map[interface{}]interface{}{},
271-
},
269+
8888,
270+
v1beta1.Service{},
272271
},
273272
} {
274273
t.Run(tt.desc, func(t *testing.T) {
275274
// these are acceptable failures, we return to the collector's default metric port
276-
port, err := adapters.ConfigToMetricsPort(logger, tt.config)
275+
port, err := adapters.ConfigToMetricsPort(tt.config)
277276
assert.NoError(t, err)
278-
assert.Equal(t, int32(8888), port)
277+
assert.Equal(t, tt.expectedPort, port)
279278
})
280279
}
281280
}

0 commit comments

Comments
 (0)