Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(observability-lib): builder to create independently grafana resources #915

Merged
merged 4 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion observability-lib/api/notification-policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,32 @@ import (
"github.com/grafana/grafana-foundation-sdk/go/alerting"
)

func objectMatchersEqual(a alerting.ObjectMatchers, b alerting.ObjectMatchers) bool {
if len(a) != len(b) {
return false
}

for i := range a {
foundMatch := false
for j := range b {
if reflect.DeepEqual(a[i], b[j]) {
foundMatch = true
break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't look like an exact match - could you leave a comment on the method explaining when a and b should be equal to each other?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@patrickhuie19 patrickhuie19 Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the tests are good 👍 - a comment saying that this is an unordered equal should suffice for the reader

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree Im adding the comment

}
}
if !foundMatch {
return false
}
}

return true
}

func policyExist(parent alerting.NotificationPolicy, newNotificationPolicy alerting.NotificationPolicy) bool {
for _, notificationPolicy := range parent.Routes {
matchersEqual := false
if notificationPolicy.ObjectMatchers != nil {
matchersEqual = reflect.DeepEqual(notificationPolicy.ObjectMatchers, newNotificationPolicy.ObjectMatchers)
matchersEqual = objectMatchersEqual(*notificationPolicy.ObjectMatchers, *newNotificationPolicy.ObjectMatchers)
}
receiversEqual := reflect.DeepEqual(notificationPolicy.Receiver, newNotificationPolicy.Receiver)
if matchersEqual && receiversEqual {
Expand Down
46 changes: 46 additions & 0 deletions observability-lib/api/notification-policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package api

import (
"testing"

"github.com/grafana/grafana-foundation-sdk/go/alerting"
"github.com/stretchr/testify/require"
)

func TestObjectMatchersEqual(t *testing.T) {
t.Run("returns true if the two object matchers are equal", func(t *testing.T) {
a := alerting.ObjectMatchers{{"team", "=", "chainlink"}}
b := alerting.ObjectMatchers{{"team", "=", "chainlink"}}

result := objectMatchersEqual(a, b)
require.True(t, result)
})

t.Run("returns true if the two object matchers with multiple matches are equal", func(t *testing.T) {
a := alerting.ObjectMatchers{
{"team", "=", "chainlink"},
{"severity", "=", "critical"},
}
b := alerting.ObjectMatchers{
{"severity", "=", "critical"},
{"team", "=", "chainlink"},
}

result := objectMatchersEqual(a, b)
require.True(t, result)
})

t.Run("returns false if the two object matchers with multiple matches are different", func(t *testing.T) {
a := alerting.ObjectMatchers{
{"team", "=", "chainlink"},
{"severity", "=", "critical"},
}
b := alerting.ObjectMatchers{
{"severity", "=", "warning"},
{"team", "=", "chainlink"},
}

result := objectMatchersEqual(a, b)
require.False(t, result)
})
}
2 changes: 1 addition & 1 deletion observability-lib/cmd/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type BuildOptions struct {
AlertsFilters string
}

func BuildDashboardWithType(options *BuildOptions) (*grafana.Dashboard, error) {
func BuildDashboardWithType(options *BuildOptions) (*grafana.Observability, error) {
switch options.TypeDashboard {
case TypeDashboardCoreNode:
return corenode.NewDashboard(&corenode.Props{
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/atlas-don/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/smartcontractkit/chainlink-common/observability-lib/grafana"
)

func NewDashboard(props *Props) (*grafana.Dashboard, error) {
func NewDashboard(props *Props) (*grafana.Observability, error) {
if props.Name == "" {
return nil, fmt.Errorf("Name is required")
}
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/atlas-don/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestNewDashboard(t *testing.T) {
if err != nil {
t.Errorf("Error creating dashboard: %v", err)
}
require.IsType(t, grafana.Dashboard{}, *testDashboard)
require.IsType(t, grafana.Observability{}, *testDashboard)
require.Equal(t, "DON OCR Dashboard", *testDashboard.Dashboard.Title)
json, errJSON := testDashboard.GenerateJSON()
if errJSON != nil {
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/capabilities/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Props struct {
}

// NewDashboard creates a Capabilities dashboard
func NewDashboard(props *Props) (*grafana.Dashboard, error) {
func NewDashboard(props *Props) (*grafana.Observability, error) {
if props.Name == "" {
return nil, fmt.Errorf("Name is required")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestNewDashboard(t *testing.T) {
if err != nil {
t.Errorf("Error creating dashboard: %v", err)
}
require.IsType(t, grafana.Dashboard{}, *testDashboard)
require.IsType(t, grafana.Observability{}, *testDashboard)
require.Equal(t, "Capabilities Dashboard", *testDashboard.Dashboard.Title)
json, errJSON := testDashboard.GenerateJSON()
if errJSON != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/smartcontractkit/chainlink-common/observability-lib/grafana"
)

func NewDashboard(props *Props) (*grafana.Dashboard, error) {
func NewDashboard(props *Props) (*grafana.Observability, error) {
if props.Name == "" {
return nil, fmt.Errorf("Name is required")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestNewDashboard(t *testing.T) {
if err != nil {
t.Errorf("Error creating dashboard: %v", err)
}
require.IsType(t, grafana.Dashboard{}, *testDashboard)
require.IsType(t, grafana.Observability{}, *testDashboard)
require.Equal(t, "Core Node Components Dashboard", *testDashboard.Dashboard.Title)
json, errJSON := testDashboard.GenerateJSON()
if errJSON != nil {
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/core-node/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

// NewDashboard creates a DON dashboard for the given OCR version
func NewDashboard(props *Props) (*grafana.Dashboard, error) {
func NewDashboard(props *Props) (*grafana.Observability, error) {
if props.Name == "" {
return nil, fmt.Errorf("Name is required")
}
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/core-node/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestNewDashboard(t *testing.T) {
if err != nil {
t.Errorf("Error creating dashboard: %v", err)
}
require.IsType(t, grafana.Dashboard{}, *testDashboard)
require.IsType(t, grafana.Observability{}, *testDashboard)
require.Equal(t, "Core Node Dashboard", *testDashboard.Dashboard.Title)
json, errJSON := testDashboard.GenerateJSON()
if errJSON != nil {
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/k8s-resources/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Props struct {
MetricsDataSource *grafana.DataSource // MetricsDataSource is the datasource for querying metrics
}

func NewDashboard(props *Props) (*grafana.Dashboard, error) {
func NewDashboard(props *Props) (*grafana.Observability, error) {
if props.Name == "" {
return nil, fmt.Errorf("Name is required")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestNewDashboard(t *testing.T) {
if err != nil {
t.Errorf("Error creating dashboard: %v", err)
}
require.IsType(t, grafana.Dashboard{}, *testDashboard)
require.IsType(t, grafana.Observability{}, *testDashboard)
require.Equal(t, "K8s resources", *testDashboard.Dashboard.Title)
json, errJSON := testDashboard.GenerateJSON()
if errJSON != nil {
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/nop-ocr/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Props struct {
OCRVersion string // OCRVersion is the version of the OCR (ocr, ocr2, ocr3)
}

func NewDashboard(props *Props) (*grafana.Dashboard, error) {
func NewDashboard(props *Props) (*grafana.Observability, error) {
if props.Name == "" {
return nil, fmt.Errorf("Name is required")
}
Expand Down
2 changes: 1 addition & 1 deletion observability-lib/dashboards/nop-ocr/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestNewDashboard(t *testing.T) {
if err != nil {
t.Errorf("Error creating dashboard: %v", err)
}
require.IsType(t, grafana.Dashboard{}, *testDashboard)
require.IsType(t, grafana.Observability{}, *testDashboard)
require.Equal(t, "NOP OCR Dashboard", *testDashboard.Dashboard.Title)
json, errJSON := testDashboard.GenerateJSON()
if errJSON != nil {
Expand Down
82 changes: 46 additions & 36 deletions observability-lib/grafana/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,23 @@ type BuilderOptions struct {
}

func NewBuilder(options *BuilderOptions) *Builder {
if options.TimeZone == "" {
options.TimeZone = common.TimeZoneBrowser
}
builder := &Builder{}

builder := &Builder{
dashboardBuilder: dashboard.NewDashboardBuilder(options.Name).
Tags(options.Tags).
Refresh(options.Refresh).
Time(options.TimeFrom, options.TimeTo).
Timezone(options.TimeZone),
if options.Name != "" {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only creating a dashboard builder if we provide a name

builder.dashboardBuilder = dashboard.NewDashboardBuilder(options.Name)
if options.Tags != nil {
builder.dashboardBuilder.Tags(options.Tags)
}
if options.Refresh != "" {
builder.dashboardBuilder.Refresh(options.Refresh)
}
if options.TimeFrom != "" && options.TimeTo != "" {
builder.dashboardBuilder.Time(options.TimeFrom, options.TimeTo)
}
if options.TimeZone == "" {
options.TimeZone = common.TimeZoneBrowser
}
builder.dashboardBuilder.Timezone(options.TimeZone)
}

if options.AlertsTags != nil {
Expand Down Expand Up @@ -104,33 +111,39 @@ func (b *Builder) AddNotificationPolicy(notificationPolicies ...*alerting.Notifi
b.notificationPoliciesBuilder = append(b.notificationPoliciesBuilder, notificationPolicies...)
}

func (b *Builder) Build() (*Dashboard, error) {
db, errBuildDashboard := b.dashboardBuilder.Build()
if errBuildDashboard != nil {
return nil, errBuildDashboard
}
func (b *Builder) Build() (*Observability, error) {
observability := Observability{}

var alerts []alerting.Rule
for _, alertBuilder := range b.alertsBuilder {
alert, errBuildAlert := alertBuilder.Build()
if errBuildAlert != nil {
return nil, errBuildAlert
if b.dashboardBuilder != nil {
db, errBuildDashboard := b.dashboardBuilder.Build()
if errBuildDashboard != nil {
return nil, errBuildDashboard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/nit, but when passing errors up the stack, it can be helpful for debugging to wrap the error with a fmt.Errorf with a description of what the consumer observed with that error, i.e.

return nil, fmt.Errorf("failure in building dashboard: %w, errBuildDashboard")

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right I'll take care of this in future iterations

}
observability.Dashboard = &db

// Add common tags to alerts
if b.alertsTags != nil && len(b.alertsTags) > 0 {
tags := maps.Clone(b.alertsTags)
maps.Copy(tags, alert.Labels)
var alerts []alerting.Rule
for _, alertBuilder := range b.alertsBuilder {
alert, errBuildAlert := alertBuilder.Build()
if errBuildAlert != nil {
return nil, errBuildAlert
}

alertBuildWithTags := alertBuilder.Labels(tags)
alertWithTags, errBuildAlertWithTags := alertBuildWithTags.Build()
if errBuildAlertWithTags != nil {
return nil, errBuildAlertWithTags
// Add common tags to alerts
if b.alertsTags != nil && len(b.alertsTags) > 0 {
tags := maps.Clone(b.alertsTags)
maps.Copy(tags, alert.Labels)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the difference between the tags and alert.Labels at this point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its the same I use tags as a name but grafana calls them Labels I could rename vars

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking, but if you could clarify in a future iteration please

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure


alertBuildWithTags := alertBuilder.Labels(tags)
alertWithTags, errBuildAlertWithTags := alertBuildWithTags.Build()
if errBuildAlertWithTags != nil {
return nil, errBuildAlertWithTags
}
alerts = append(alerts, alertWithTags)
} else {
alerts = append(alerts, alert)
}
alerts = append(alerts, alertWithTags)
} else {
alerts = append(alerts, alert)
}
observability.Alerts = alerts
}

var contactPoints []alerting.ContactPoint
Expand All @@ -141,6 +154,7 @@ func (b *Builder) Build() (*Dashboard, error) {
}
contactPoints = append(contactPoints, contactPoint)
}
observability.ContactPoints = contactPoints

var notificationPolicies []alerting.NotificationPolicy
for _, notificationPolicyBuilder := range b.notificationPoliciesBuilder {
Expand All @@ -150,11 +164,7 @@ func (b *Builder) Build() (*Dashboard, error) {
}
notificationPolicies = append(notificationPolicies, notificationPolicy)
}
observability.NotificationPolicies = notificationPolicies

return &Dashboard{
Dashboard: &db,
Alerts: alerts,
ContactPoints: contactPoints,
NotificationPolicies: notificationPolicies,
}, nil
return &observability, nil
}
Loading
Loading