Skip to content

Commit 4fc3526

Browse files
celian-garciajmacd
andcommitted
[receiver/azuremonitorreceiver] feat: multi subscriptions support and automatic discovery
Co-authored-by: Joshua MacDonald <[email protected]> Signed-off-by: Célian Garcia <[email protected]>
1 parent e4559ec commit 4fc3526

File tree

16 files changed

+1126
-870
lines changed

16 files changed

+1126
-870
lines changed

.chloggen/feat_discovery.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: breaking
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: receiver/azuremonitorreceiver
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "multi subscriptions support and automatic discovery"
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [36612]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

receiver/azuremonitorreceiver/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ This receiver scrapes Azure Monitor API for resources metrics.
1818

1919
The following settings are required:
2020

21-
- `subscription_id`
21+
- `subscription_ids`: list of subscriptions on which the resource's metrics are collected
22+
- or `discover_subscriptions`: (default = `false`) If set to true, will collect metrics from all subscriptions in the tenant.
2223

2324
The following settings are optional:
2425

@@ -78,7 +79,7 @@ Using [Service Principal](https://learn.microsoft.com/en-us/azure/developer/go/a
7879
```yaml
7980
receivers:
8081
azuremonitor:
81-
subscription_id: "${subscription_id}"
82+
subscription_ids: ["${subscription_id}"]
8283
tenant_id: "${tenant_id}"
8384
client_id: "${client_id}"
8485
client_secret: "${env:CLIENT_SECRET}"
@@ -98,7 +99,7 @@ Using [Azure Workload Identity](https://learn.microsoft.com/en-us/azure/develope
9899
```yaml
99100
receivers:
100101
azuremonitor:
101-
subscription_id: "${subscription_id}"
102+
subscription_ids: ["${subscription_id}"]
102103
auth: "workload_identity"
103104
tenant_id: "${env:AZURE_TENANT_ID}"
104105
client_id: "${env:AZURE_CLIENT_ID}"
@@ -110,7 +111,7 @@ Using [Managed Identity](https://learn.microsoft.com/en-us/azure/developer/go/az
110111
```yaml
111112
receivers:
112113
azuremonitor:
113-
subscription_id: "${subscription_id}"
114+
subscription_ids: ["${subscription_id}"]
114115
auth: "managed_identity"
115116
client_id: "${env:AZURE_CLIENT_ID}"
116117
```
@@ -120,7 +121,7 @@ Using [Environment Variables](https://learn.microsoft.com/en-us/azure/developer/
120121
```yaml
121122
receivers:
122123
azuremonitor:
123-
subscription_id: "${subscription_id}"
124+
subscription_ids: ["${subscription_id}"]
124125
auth: "default_credentials"
125126
```
126127

receiver/azuremonitorreceiver/config.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ const (
2121

2222
var (
2323
// Predefined error responses for configuration validation failures
24-
errMissingTenantID = errors.New(`TenantID" is not specified in config`)
25-
errMissingSubscriptionID = errors.New(`SubscriptionID" is not specified in config`)
26-
errMissingClientID = errors.New(`ClientID" is not specified in config`)
27-
errMissingClientSecret = errors.New(`ClientSecret" is not specified in config`)
28-
errMissingFedTokenFile = errors.New(`FederatedTokenFile is not specified in config`)
29-
errInvalidCloud = errors.New(`Cloud" is invalid`)
24+
errMissingTenantID = errors.New(`"TenantID" is not specified in config`)
25+
errMissingSubscriptionIDs = errors.New(`neither "SubscriptionIDs" nor "DiscoverSubscription" is specified in the config`)
26+
errMissingClientID = errors.New(`"ClientID" is not specified in config`)
27+
errMissingClientSecret = errors.New(`"ClientSecret" is not specified in config`)
28+
errMissingFedTokenFile = errors.New(`"FederatedTokenFile"" is not specified in config`)
29+
errInvalidCloud = errors.New(`"Cloud" is invalid`)
3030

3131
monitorServices = []string{
3232
"Microsoft.EventGrid/eventSubscriptions",
@@ -240,7 +240,8 @@ type Config struct {
240240
scraperhelper.ControllerConfig `mapstructure:",squash"`
241241
MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"`
242242
Cloud string `mapstructure:"cloud"`
243-
SubscriptionID string `mapstructure:"subscription_id"`
243+
SubscriptionIDs []string `mapstructure:"subscription_ids"`
244+
DiscoverSubscriptions bool `mapstructure:"discover_subscriptions"`
244245
Authentication string `mapstructure:"auth"`
245246
TenantID string `mapstructure:"tenant_id"`
246247
ClientID string `mapstructure:"client_id"`
@@ -266,8 +267,8 @@ const (
266267

267268
// Validate validates the configuration by checking for missing or invalid fields
268269
func (c Config) Validate() (err error) {
269-
if c.SubscriptionID == "" {
270-
err = multierr.Append(err, errMissingSubscriptionID)
270+
if len(c.SubscriptionIDs) == 0 && !c.DiscoverSubscriptions {
271+
err = multierr.Append(err, errMissingSubscriptionIDs)
271272
}
272273

273274
switch c.Authentication {

receiver/azuremonitorreceiver/factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func NewFactory() receiver.Factory {
3232
receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability))
3333
}
3434

35+
// createDefaultConfig creates the default configuration for the receiver
3536
func createDefaultConfig() component.Config {
3637
cfg := scraperhelper.NewDefaultControllerConfig()
3738
cfg.CollectionInterval = defaultCollectionInterval

receiver/azuremonitorreceiver/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
88
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0
99
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
10+
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0
1011
github.com/google/go-cmp v0.7.0
1112
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.122.0
1213
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.122.0

receiver/azuremonitorreceiver/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metadata
5+
6+
// MetricsBuilderConfig is a structural subset of an otherwise 1-1 copy of metadata.yaml
7+
type MetricsBuilderConfig struct {
8+
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
9+
}
10+
11+
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
12+
return MetricsBuilderConfig{
13+
// Don't use DefaultResourceAttributesConfig() here to be able to set true by default.
14+
ResourceAttributes: ResourceAttributesConfig{
15+
AzuremonitorSubscriptionID: ResourceAttributeConfig{
16+
Enabled: true,
17+
},
18+
AzuremonitorTenantID: ResourceAttributeConfig{
19+
Enabled: true,
20+
},
21+
},
22+
}
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metadata
5+
6+
import (
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
"go.opentelemetry.io/collector/confmap/confmaptest"
12+
)
13+
14+
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
15+
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
16+
require.NoError(t, err)
17+
sub, err := cm.Sub(name)
18+
require.NoError(t, err)
19+
cfg := DefaultMetricsBuilderConfig()
20+
require.NoError(t, sub.Unmarshal(&cfg))
21+
return cfg
22+
}

receiver/azuremonitorreceiver/internal/metadata/metrics.go

Lines changed: 49 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -11,137 +11,102 @@ import (
1111
"time"
1212

1313
"go.opentelemetry.io/collector/component"
14-
"go.opentelemetry.io/collector/confmap"
1514
"go.opentelemetry.io/collector/pdata/pcommon"
1615
"go.opentelemetry.io/collector/pdata/pmetric"
1716
"go.opentelemetry.io/collector/receiver"
1817
)
1918

2019
const metricsPrefix = "azure_"
2120

22-
// ResourceAttributeSettings provides common settings for a particular metric.
23-
type ResourceAttributeSettings struct {
24-
Enabled bool `mapstructure:"enabled"`
25-
26-
enabledProvidedByUser bool
21+
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
22+
// required to produce metric representation defined in metadata and user config.
23+
type MetricsBuilder struct {
24+
config MetricsBuilderConfig // config of the metrics builder.
25+
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
26+
metricsCapacity int // maximum observed number of metrics per resource.
27+
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
28+
buildInfo component.BuildInfo // contains version information
29+
metrics map[string]*metricAzureAbstract
2730
}
2831

29-
func (ras *ResourceAttributeSettings) Unmarshal(parser *confmap.Conf) error {
30-
if parser == nil {
31-
return nil
32-
}
33-
err := parser.Unmarshal(ras)
34-
if err != nil {
35-
return err
36-
}
37-
ras.enabledProvidedByUser = parser.IsSet("enabled")
38-
return nil
32+
// MetricBuilderOption applies changes to default metrics builder.
33+
type MetricBuilderOption interface {
34+
apply(*MetricsBuilder)
3935
}
4036

41-
// ResourceAttributesSettings provides settings for azuremonitorreceiver metrics.
42-
type ResourceAttributesSettings struct {
43-
AzureMonitorSubscriptionID ResourceAttributeSettings `mapstructure:"azuremonitor.subscription_id"`
44-
AzureMonitorTenantID ResourceAttributeSettings `mapstructure:"azuremonitor.tenant_id"`
37+
type metricBuilderOptionFunc func(mb *MetricsBuilder)
38+
39+
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
40+
mbof(mb)
4541
}
4642

47-
func DefaultResourceAttributesSettings() ResourceAttributesSettings {
48-
return ResourceAttributesSettings{
49-
AzureMonitorSubscriptionID: ResourceAttributeSettings{
50-
Enabled: true,
51-
},
52-
AzureMonitorTenantID: ResourceAttributeSettings{
53-
Enabled: true,
54-
},
55-
}
43+
// WithStartTime sets startTime on the metrics builder.
44+
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
45+
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
46+
mb.startTime = startTime
47+
})
5648
}
5749

58-
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...metricBuilderOption) *MetricsBuilder {
50+
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...MetricBuilderOption) *MetricsBuilder {
5951
mb := &MetricsBuilder{
60-
startTime: pcommon.NewTimestampFromTime(time.Now()),
61-
metricsBuffer: pmetric.NewMetrics(),
62-
buildInfo: settings.BuildInfo,
63-
resourceAttributesSettings: mbc.ResourceAttributes,
64-
metrics: map[string]*metricAzureAbstract{},
52+
config: mbc,
53+
startTime: pcommon.NewTimestampFromTime(time.Now()),
54+
metricsBuffer: pmetric.NewMetrics(),
55+
buildInfo: settings.BuildInfo,
56+
metrics: map[string]*metricAzureAbstract{},
6557
}
6658
for _, op := range options {
67-
op(mb)
59+
op.apply(mb)
6860
}
6961
return mb
7062
}
7163

72-
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
73-
return MetricsBuilderConfig{
74-
ResourceAttributes: DefaultResourceAttributesSettings(),
75-
}
76-
}
77-
78-
// MetricsBuilderConfig is a structural subset of an otherwise 1-1 copy of metadata.yaml
79-
type MetricsBuilderConfig struct {
80-
ResourceAttributes ResourceAttributesSettings `mapstructure:"resource_attributes"`
64+
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
65+
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
66+
return NewResourceBuilder(mb.config.ResourceAttributes)
8167
}
8268

83-
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
84-
// required to produce metric representation defined in metadata and user settings.
85-
type MetricsBuilder struct {
86-
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
87-
metricsCapacity int // maximum observed number of metrics per resource.
88-
resourceCapacity int // maximum observed number of resource attributes.
89-
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
90-
buildInfo component.BuildInfo // contains version information
91-
resourceAttributesSettings ResourceAttributesSettings
92-
metrics map[string]*metricAzureAbstract
93-
}
94-
95-
// metricBuilderOption applies changes to default metrics builder.
96-
type metricBuilderOption func(*MetricsBuilder)
97-
9869
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
9970
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
10071
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
10172
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
10273
}
103-
if mb.resourceCapacity < rm.Resource().Attributes().Len() {
104-
mb.resourceCapacity = rm.Resource().Attributes().Len()
105-
}
10674
}
10775

10876
// ResourceMetricsOption applies changes to provided resource metrics.
109-
type ResourceMetricsOption func(ResourceAttributesSettings, pmetric.ResourceMetrics)
77+
type ResourceMetricsOption interface {
78+
apply(pmetric.ResourceMetrics)
79+
}
11080

111-
// WithAzureMonitorSubscriptionID sets provided value as "azuremonitor.subscription_id" attribute for current resource.
112-
func WithAzureMonitorSubscriptionID(val string) ResourceMetricsOption {
113-
return func(ras ResourceAttributesSettings, rm pmetric.ResourceMetrics) {
114-
if ras.AzureMonitorSubscriptionID.Enabled {
115-
rm.Resource().Attributes().PutStr("azuremonitor.subscription_id", val)
116-
}
117-
}
81+
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
82+
83+
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
84+
rmof(rm)
11885
}
11986

120-
// WithAzuremonitorTenantID sets provided value as "azuremonitor.tenant_id" attribute for current resource.
121-
func WithAzureMonitorTenantID(val string) ResourceMetricsOption {
122-
return func(ras ResourceAttributesSettings, rm pmetric.ResourceMetrics) {
123-
if ras.AzureMonitorTenantID.Enabled {
124-
rm.Resource().Attributes().PutStr("azuremonitor.tenant_id", val)
125-
}
126-
}
87+
// WithResource sets the provided resource on the emitted ResourceMetrics.
88+
// It's recommended to use ResourceBuilder to create the resource.
89+
func WithResource(res pcommon.Resource) ResourceMetricsOption {
90+
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
91+
res.CopyTo(rm.Resource())
92+
})
12793
}
12894

12995
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
13096
// recording another set of data points as part of another resource. This function can be helpful when one scraper
13197
// needs to emit metrics from several resources. Otherwise calling this function is not required,
13298
// just `Emit` function can be called instead.
13399
// Resource attributes should be provided as ResourceMetricsOption arguments.
134-
func (mb *MetricsBuilder) EmitForResource(rmo ...ResourceMetricsOption) {
100+
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
135101
rm := pmetric.NewResourceMetrics()
136-
rm.Resource().Attributes().EnsureCapacity(mb.resourceCapacity)
137102
ils := rm.ScopeMetrics().AppendEmpty()
138103
ils.Scope().SetName(ScopeName)
139104
ils.Scope().SetVersion(mb.buildInfo.Version)
140105
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
141106
mb.EmitAllMetrics(ils)
142107

143-
for _, op := range rmo {
144-
op(mb.resourceAttributesSettings, rm)
108+
for _, op := range options {
109+
op.apply(rm)
145110
}
146111
if ils.Metrics().Len() > 0 {
147112
mb.updateCapacity(rm)
@@ -161,10 +126,10 @@ func (mb *MetricsBuilder) Emit(rmo ...ResourceMetricsOption) pmetric.Metrics {
161126

162127
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
163128
// and metrics builder should update its startTime and reset it's internal state accordingly.
164-
func (mb *MetricsBuilder) Reset(options ...metricBuilderOption) {
129+
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
165130
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
166131
for _, op := range options {
167-
op(mb)
132+
op.apply(mb)
168133
}
169134
}
170135

0 commit comments

Comments
 (0)