Skip to content

Commit

Permalink
feat: PC-13027 PC-13029 Add Azure Monitor managed service for Prometh… (
Browse files Browse the repository at this point in the history
#437)

## Motivation

Support new integration with Azure Monitor managed service for
Prometheus

## Summary

New fields in Agent spec:
```
 spec:
    azurePrometheus: 
      url: ""
      tenantId: ""
```
- `azurePrometheus` - name of the new integration
- `url` - is the `Endpoint - query URL` in Azure Prometheus ([How to
find the Prometheus
URL?](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/prometheus-api-promql#query-endpoint))
- `tenentId` - Azure [tenent
ID](https://learn.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id#find-your-azure-ad-tenant)

New fields in Metric Spec, example:
```
 countMetrics:
        incremental: false
        good:
          azurePrometheus:
            promql: ""
        total:
          azurePrometheus:
            promql: ""
```
- `azurePrometheus` - name of the new integration
- `promql` - a Prometheus query in the
[PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/)
(Prometheus Query Language) that allows the user to select and aggregate
time-series data in real-tim

## Release Notes

Added Azure Monitor managed service for Prometheus Agent, and Metric
Spec extending both `v1alpha.Agent.Spec` and `v1alpha.SLO.MetricSpec`.
  • Loading branch information
dawidwisn authored Jun 19, 2024
1 parent ea8f391 commit cd7b62d
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 4 deletions.
9 changes: 9 additions & 0 deletions manifest/v1alpha/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Spec struct {
Generic *GenericConfig `json:"generic,omitempty"`
Honeycomb *HoneycombConfig `json:"honeycomb,omitempty"`
LogicMonitor *LogicMonitorConfig `json:"logicMonitor,omitempty"`
AzurePrometheus *AzurePrometheusConfig `json:"azurePrometheus,omitempty"`
HistoricalDataRetrieval *v1alpha.HistoricalDataRetrieval `json:"historicalDataRetrieval,omitempty"`
QueryDelay *v1alpha.QueryDelay `json:"queryDelay,omitempty"`
// Interval, Timeout and Jitter are readonly and cannot be set via API
Expand Down Expand Up @@ -139,6 +140,8 @@ func (spec Spec) GetType() (v1alpha.DataSourceType, error) {
return v1alpha.Honeycomb, nil
case spec.LogicMonitor != nil:
return v1alpha.LogicMonitor, nil
case spec.AzurePrometheus != nil:
return v1alpha.AzurePrometheus, nil
}
return 0, errors.New("unknown agent type")
}
Expand Down Expand Up @@ -258,3 +261,9 @@ type HoneycombConfig struct{}
type LogicMonitorConfig struct {
Account string `json:"account"`
}

// AzurePrometheusConfig represents content of Azure Monitor managed service for Prometheus typical for Agent Object.
type AzurePrometheusConfig struct {
URL string `json:"url"`
TenantID string `json:"tenantId"`
}
18 changes: 18 additions & 0 deletions manifest/v1alpha/agent/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ var specValidation = validation.New[Spec](
validation.ForPointer(func(s Spec) *LogicMonitorConfig { return s.LogicMonitor }).
WithName("logicMonitor").
Include(logicMonitorValidation),
validation.ForPointer(func(s Spec) *AzurePrometheusConfig { return s.AzurePrometheus }).
WithName("azurePrometheus").
Include(azurePrometheusValidation),
)

var (
Expand Down Expand Up @@ -201,6 +204,16 @@ var (
Required().
Rules(validation.StringNotEmpty()),
)
azurePrometheusValidation = validation.New[AzurePrometheusConfig](
validation.For(func(a AzurePrometheusConfig) string { return a.URL }).
WithName("url").
Required().
Rules(validation.StringURL()),
validation.For(func(a AzurePrometheusConfig) string { return a.TenantID }).
WithName("tenantId").
Required().
Rules(validation.StringUUID()),
)
// URL only.
prometheusValidation = newURLValidator(func(p PrometheusConfig) string { return p.URL })
appDynamicsValidation = newURLValidator(func(a AppDynamicsConfig) string { return a.URL })
Expand Down Expand Up @@ -371,6 +384,11 @@ var exactlyOneDataSourceTypeValidationRule = validation.NewSingleRule(func(spec
return err
}
}
if spec.AzurePrometheus != nil {
if err := typesMatch(v1alpha.AzurePrometheus); err != nil {
return err
}
}
if onlyType == 0 {
return errors.New("must have exactly one data source type, none were provided")
}
Expand Down
72 changes: 72 additions & 0 deletions manifest/v1alpha/agent/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,72 @@ func TestValidateSpec_AzureMonitor(t *testing.T) {
})
}

func TestValidateSpec_LogicMonitor(t *testing.T) {
t.Run("passes", func(t *testing.T) {
agent := validAgent(v1alpha.LogicMonitor)
err := validate(agent)
testutils.AssertNoError(t, agent, err)
})
t.Run("required account", func(t *testing.T) {
agent := validAgent(v1alpha.LogicMonitor)
agent.Spec.LogicMonitor.Account = ""
err := validate(agent)
testutils.AssertContainsErrors(t, agent, err, 1, testutils.ExpectedError{
Prop: "spec.logicMonitor.account",
Code: validation.ErrorCodeRequired,
})
})
}

func TestValidateSpec_AzurePrometheus(t *testing.T) {
t.Run("passes", func(t *testing.T) {
agent := validAgent(v1alpha.AzurePrometheus)
err := validate(agent)
testutils.AssertNoError(t, agent, err)
})
t.Run("invalid tenantId", func(t *testing.T) {
agent := validAgent(v1alpha.AzurePrometheus)
agent.Spec.AzurePrometheus.TenantID = "invalid"
err := validate(agent)
testutils.AssertContainsErrors(t, agent, err, 1, testutils.ExpectedError{
Prop: "spec.azurePrometheus.tenantId",
Code: validation.ErrorCodeStringUUID,
})
})
t.Run("required fields", func(t *testing.T) {
agent := validAgent(v1alpha.AzurePrometheus)
agent.Spec.AzurePrometheus.URL = ""
agent.Spec.AzurePrometheus.TenantID = ""
err := validate(agent)
testutils.AssertContainsErrors(t, agent, err, 2,
testutils.ExpectedError{
Prop: "spec.azurePrometheus.url",
Code: validation.ErrorCodeRequired,
},
testutils.ExpectedError{
Prop: "spec.azurePrometheus.tenantId",
Code: validation.ErrorCodeRequired,
},
)
})
t.Run("invalid fields", func(t *testing.T) {
agent := validAgent(v1alpha.AzurePrometheus)
agent.Spec.AzurePrometheus.URL = "invalid"
agent.Spec.AzurePrometheus.TenantID = strings.Repeat("l", 256)
err := validate(agent)
testutils.AssertContainsErrors(t, agent, err, 2,
testutils.ExpectedError{
Prop: "spec.azurePrometheus.url",
Code: validation.ErrorCodeStringURL,
},
testutils.ExpectedError{
Prop: "spec.azurePrometheus.tenantId",
Code: validation.ErrorCodeStringUUID,
},
)
})
}

func validAgent(typ v1alpha.DataSourceType) Agent {
spec := validAgentSpec(typ)
spec.Description = fmt.Sprintf("Example %s Agent", typ)
Expand Down Expand Up @@ -874,6 +940,12 @@ func validAgentSpec(typ v1alpha.DataSourceType) Spec {
Account: "account",
},
},
v1alpha.AzurePrometheus: {
AzurePrometheus: &AzurePrometheusConfig{
URL: "https://prometheus-service.monitoring:8080",
TenantID: "e190c630-8873-11ee-b9d1-0242ac120002",
},
},
}

return specs[typ]
Expand Down
6 changes: 6 additions & 0 deletions manifest/v1alpha/data_sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ AzureMonitor
Generic
Honeycomb
LogicMonitor
AzurePrometheus
)*/
type DataSourceType int

Expand Down Expand Up @@ -321,6 +322,7 @@ var agentDataRetrievalMaxDuration = map[DataSourceType]HistoricalRetrievalDurati
AzureMonitor: {Value: ptr(30), Unit: HRDDay},
Honeycomb: {Value: ptr(7), Unit: HRDDay},
GoogleCloudMonitoring: {Value: ptr(30), Unit: HRDDay},
AzurePrometheus: {Value: ptr(30), Unit: HRDDay},
}

var directDataRetrievalMaxDuration = map[DataSourceType]HistoricalRetrievalDuration{
Expand Down Expand Up @@ -469,6 +471,10 @@ func GetQueryDelayDefaults() QueryDelayDefaults {
Value: ptr(2),
Unit: Minute,
},
AzurePrometheus: {
Value: ptr(0),
Unit: Second,
},
}
}

Expand Down
7 changes: 6 additions & 1 deletion manifest/v1alpha/data_sources_enum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions manifest/v1alpha/slo/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type MetricSpec struct {
Generic *GenericMetric `json:"generic,omitempty"`
Honeycomb *HoneycombMetric `json:"honeycomb,omitempty"`
LogicMonitor *LogicMonitorMetric `json:"logicMonitor,omitempty"`
AzurePrometheus *AzurePrometheusMetric `json:"azurePrometheus,omitempty"`
}

func (s *Spec) containsIndicatorRawMetric() bool {
Expand Down Expand Up @@ -265,6 +266,8 @@ func (m *MetricSpec) DataSourceType() v1alpha.DataSourceType {
return v1alpha.Honeycomb
case m.LogicMonitor != nil:
return v1alpha.LogicMonitor
case m.AzurePrometheus != nil:
return v1alpha.AzurePrometheus
default:
return 0
}
Expand Down Expand Up @@ -349,6 +352,8 @@ func (m *MetricSpec) Query() interface{} {
return m.Honeycomb
case v1alpha.LogicMonitor:
return m.LogicMonitor
case v1alpha.AzurePrometheus:
return m.AzurePrometheus
default:
return nil
}
Expand Down
15 changes: 15 additions & 0 deletions manifest/v1alpha/slo/metrics_azure_prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package slo

import "github.com/nobl9/nobl9-go/internal/validation"

// AzurePrometheusMetric represents metric from Azure Monitor managed service for Prometheus
type AzurePrometheusMetric struct {
PromQL string `json:"promql"`
}

var azurePrometheusValidation = validation.New[AzurePrometheusMetric](
validation.For(func(p AzurePrometheusMetric) string { return p.PromQL }).
WithName("promql").
Required().
Rules(validation.StringNotEmpty()),
)
35 changes: 35 additions & 0 deletions manifest/v1alpha/slo/metrics_azure_prometheus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package slo

import (
"testing"

"github.com/nobl9/nobl9-go/internal/testutils"
"github.com/nobl9/nobl9-go/internal/validation"
"github.com/nobl9/nobl9-go/manifest/v1alpha"
)

func TestAzurePrometheus(t *testing.T) {
t.Run("passes", func(t *testing.T) {
slo := validRawMetricSLO(v1alpha.AzurePrometheus)
err := validate(slo)
testutils.AssertNoError(t, slo, err)
})
t.Run("required", func(t *testing.T) {
slo := validRawMetricSLO(v1alpha.AzurePrometheus)
slo.Spec.Objectives[0].RawMetric.MetricQuery.AzurePrometheus = &AzurePrometheusMetric{}
err := validate(slo)
testutils.AssertContainsErrors(t, slo, err, 1, testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.azurePrometheus.promql",
Code: validation.ErrorCodeRequired,
})
})
t.Run("empty", func(t *testing.T) {
slo := validRawMetricSLO(v1alpha.AzurePrometheus)
slo.Spec.Objectives[0].RawMetric.MetricQuery.AzurePrometheus.PromQL = ""
err := validate(slo)
testutils.AssertContainsErrors(t, slo, err, 1, testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.azurePrometheus.promql",
Code: validation.ErrorCodeRequired,
})
})
}
32 changes: 29 additions & 3 deletions manifest/v1alpha/slo/metrics_logic_monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,35 @@ func TestLogicMonitor(t *testing.T) {
testutils.AssertNoError(t, slo, err)
})
t.Run("required", func(t *testing.T) {
slo := validRawMetricSLO(v1alpha.LogicMonitor)
slo.Spec.Objectives[0].RawMetric.MetricQuery.LogicMonitor = &LogicMonitorMetric{}
err := validate(slo)
testutils.AssertContainsErrors(t, slo, err, 4,
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.queryType",
Code: validation.ErrorCodeRequired,
},
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.deviceDataSourceInstanceId",
Code: validation.ErrorCodeRequired,
},
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.graphId",
Code: validation.ErrorCodeRequired,
},
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.line",
Code: validation.ErrorCodeRequired,
},
)
})
t.Run("invalid fields", func(t *testing.T) {
slo := validRawMetricSLO(v1alpha.LogicMonitor)
slo.Spec.Objectives[0].RawMetric.MetricQuery.LogicMonitor = &LogicMonitorMetric{
QueryType: "wrongQueryType",
QueryType: "wrong-type",
DeviceDataSourceInstanceID: -1,
GraphID: -1,
Line: "",
}
err := validate(slo)
testutils.AssertContainsErrors(t, slo, err, 4,
Expand All @@ -27,11 +53,11 @@ func TestLogicMonitor(t *testing.T) {
},
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.deviceDataSourceInstanceId",
Code: validation.ErrorCodeRequired,
Code: validation.ErrorCodeGreaterThanOrEqualTo,
},
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.graphId",
Code: validation.ErrorCodeRequired,
Code: validation.ErrorCodeGreaterThanOrEqualTo,
},
testutils.ExpectedError{
Prop: "spec.objectives[0].rawMetric.query.logicMonitor.line",
Expand Down
9 changes: 9 additions & 0 deletions manifest/v1alpha/slo/metrics_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ var metricSpecValidation = validation.New[MetricSpec](
validation.ForPointer(func(m MetricSpec) *LogicMonitorMetric { return m.LogicMonitor }).
WithName("logicMonitor").
Include(logicMonitorValidation),
validation.ForPointer(func(m MetricSpec) *AzurePrometheusMetric { return m.AzurePrometheus }).
WithName("azurePrometheus").
Include(azurePrometheusValidation),
)

var badOverTotalEnabledSources = []v1alpha.DataSourceType{
Expand All @@ -187,6 +190,7 @@ var badOverTotalEnabledSources = []v1alpha.DataSourceType{
v1alpha.AzureMonitor,
v1alpha.Honeycomb,
v1alpha.LogicMonitor,
v1alpha.AzurePrometheus,
}

// Support for bad/total metrics will be enabled gradually.
Expand Down Expand Up @@ -353,6 +357,11 @@ func validateExactlyOneMetricSpecType(metrics ...*MetricSpec) error {
return err
}
}
if metric.AzurePrometheus != nil {
if err := typesMatch(v1alpha.AzurePrometheus); err != nil {
return err
}
}
}
if onlyType == 0 {
return errors.New("must have exactly one metric spec type, none were provided")
Expand Down
3 changes: 3 additions & 0 deletions manifest/v1alpha/slo/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,9 @@ fetch consumed_api
GraphID: 11354,
Line: "MAXRTT",
}},
v1alpha.AzurePrometheus: {AzurePrometheus: &AzurePrometheusMetric{
PromQL: "sum(rate(prometheus_http_requests_total[1h]))",
}},
}

func ptr[T any](v T) *T { return &v }
Expand Down

0 comments on commit cd7b62d

Please sign in to comment.