Skip to content

Commit

Permalink
feat: add metric gauge notification api
Browse files Browse the repository at this point in the history
  • Loading branch information
mabdh committed Aug 29, 2024
1 parent 4724fc6 commit 5371b23
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 107 deletions.
4 changes: 4 additions & 0 deletions core/notification/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

saltlog "github.com/goto/salt/log"
"github.com/mitchellh/hashstructure/v2"
"go.opentelemetry.io/otel/metric"
"golang.org/x/exp/maps"

"github.com/goto/siren/core/log"
Expand Down Expand Up @@ -56,6 +57,9 @@ type Service struct {
deps Deps
routerMap map[string]Router
notifierPlugins map[string]Notifier

metricNotificationCount metric.Int64Gauge

Check failure on line 61 in core/notification/service.go

View workflow job for this annotation

GitHub Actions / golangci-lint

field `metricNotificationCount` is unused (unused)
metricReceiverSelectorCount metric.Int64Gauge

Check failure on line 62 in core/notification/service.go

View workflow job for this annotation

GitHub Actions / golangci-lint

field `metricReceiverSelectorCount` is unused (unused)
}

type Deps struct {
Expand Down
25 changes: 13 additions & 12 deletions internal/api/v1beta1/alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
sirenv1beta1 "github.com/goto/siren/proto/gotocompany/siren/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)

Expand All @@ -31,8 +32,8 @@ func TestGRPCServer_ListAlerts(t *testing.T) {
StartTime: 100,
EndTime: 200,
}).Return(dummyAlerts, nil).Once()
dummyGRPCServer := v1beta1.NewGRPCServer(nil, api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService})

dummyGRPCServer, err := v1beta1.NewGRPCServer(nil, api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService})
require.NoError(t, err)
dummyReq := &sirenv1beta1.ListAlertsRequest{
ResourceName: "foo",
ProviderId: 1,
Expand All @@ -53,8 +54,8 @@ func TestGRPCServer_ListAlerts(t *testing.T) {

t.Run("should return error Internal if getting alert history failed", func(t *testing.T) {
mockedAlertService := &mocks.AlertService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService})

dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService})
require.NoError(t, err)
mockedAlertService.EXPECT().List(mock.AnythingOfType("context.todoCtx"), alert.Filter{
ProviderID: 1,
ResourceName: "foo",
Expand Down Expand Up @@ -173,8 +174,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) {
Return(dummyAlerts, nil).Once()
mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return(nil, nil)

dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService})

dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService})
require.NoError(t, err)
res, err := dummyGRPCServer.CreateAlerts(context.TODO(), dummyReq)
assert.Equal(t, 1, len(res.GetAlerts()))
assert.Equal(t, uint64(1), res.GetAlerts()[0].GetId())
Expand Down Expand Up @@ -281,8 +282,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) {
Return(dummyAlerts, nil).Once()
mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return(nil, nil)

dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService})

dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService})
require.NoError(t, err)
res, err := dummyGRPCServer.CreateAlerts(context.TODO(), dummyReq)
assert.Equal(t, 1, len(res.GetAlerts()))
assert.Equal(t, uint64(1), res.GetAlerts()[0].GetId())
Expand All @@ -297,8 +298,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) {

t.Run("should return error Internal if getting alert history failed", func(t *testing.T) {
mockedAlertService := &mocks.AlertService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService})

dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService})
require.NoError(t, err)
mockedAlertService.EXPECT().CreateAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), payload).
Return(nil, errors.New("random error")).Once()

Expand Down Expand Up @@ -456,8 +457,8 @@ func TestGRPCServer_CreateAlertHistory(t *testing.T) {
TriggeredAt: time.Now(),
}}

dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService})

dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{AlertService: mockedAlertService, NotificationService: mockNotificationService})
require.NoError(t, err)
mockedAlertService.EXPECT().CreateAlerts(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("string"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), payload).
Return(dummyAlerts, nil).Once()
mockNotificationService.EXPECT().Dispatch(mock.AnythingOfType("context.todoCtx"), mock.AnythingOfType("[]notification.Notification")).Return(nil, nil)
Expand Down
52 changes: 35 additions & 17 deletions internal/api/v1beta1/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
sirenv1beta1 "github.com/goto/siren/proto/gotocompany/siren/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)

Expand All @@ -26,7 +27,8 @@ func TestGRPCServer_ListNamespaces(t *testing.T) {

t.Run("should return list of all namespaces", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
dummyResult := []namespace.Namespace{
{
ID: 1,
Expand All @@ -53,7 +55,8 @@ func TestGRPCServer_ListNamespaces(t *testing.T) {

t.Run("should return Internal if getting namespaces failed", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().List(mock.AnythingOfType("context.todoCtx")).
Return(nil, errors.New("random error")).Once()
res, err := dummyGRPCServer.ListNamespaces(context.TODO(), &sirenv1beta1.ListNamespacesRequest{})
Expand All @@ -63,7 +66,8 @@ func TestGRPCServer_ListNamespaces(t *testing.T) {

t.Run("should return Internal if NewStruct conversion failed", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
credentials["bar"] = string([]byte{0xff})
dummyResult := []namespace.Namespace{
{
Expand Down Expand Up @@ -110,7 +114,8 @@ func TestGRPCServer_CreateNamespaces(t *testing.T) {

t.Run("should create a namespace", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), payload).Run(func(_a0 context.Context, _a1 *namespace.Namespace) {
_a1.ID = generatedID
}).Return(nil).Once()
Expand All @@ -121,7 +126,8 @@ func TestGRPCServer_CreateNamespaces(t *testing.T) {

t.Run("should return error Internal if creating namespaces failed", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), payload).Return(errors.New("random error")).Once()
res, err := dummyGRPCServer.CreateNamespace(context.TODO(), request)
assert.Nil(t, res)
Expand All @@ -130,7 +136,8 @@ func TestGRPCServer_CreateNamespaces(t *testing.T) {

t.Run("should return error Invalid Argument if create service return err invalid", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)

mockedNamespaceService.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), payload).Return(errors.ErrInvalid).Once()
res, err := dummyGRPCServer.CreateNamespace(context.TODO(), request)
Expand All @@ -141,7 +148,8 @@ func TestGRPCServer_CreateNamespaces(t *testing.T) {

t.Run("should return error AlreadyExists if create service return err conflict", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)

mockedNamespaceService.EXPECT().Create(mock.AnythingOfType("context.todoCtx"), payload).Return(errors.ErrConflict).Once()
res, err := dummyGRPCServer.CreateNamespace(context.TODO(), request)
Expand All @@ -159,7 +167,8 @@ func TestGRPCServer_GetNamespace(t *testing.T) {

t.Run("should get the namespace", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
dummyResult := &namespace.Namespace{
ID: 1,
Provider: provider.Provider{
Expand All @@ -184,7 +193,8 @@ func TestGRPCServer_GetNamespace(t *testing.T) {

t.Run("should return error Invalid Argument if no namespace found", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Get(mock.AnythingOfType("context.todoCtx"), uint64(1)).Return(nil, errors.ErrNotFound.WithCausef("some error")).Once()
res, err := dummyGRPCServer.GetNamespace(context.TODO(),
&sirenv1beta1.GetNamespaceRequest{Id: uint64(1)})
Expand All @@ -194,7 +204,8 @@ func TestGRPCServer_GetNamespace(t *testing.T) {

t.Run("should return error Internal if getting namespace fails", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Get(mock.AnythingOfType("context.todoCtx"), uint64(1)).
Return(nil, errors.New("random error")).Once()
res, err := dummyGRPCServer.GetNamespace(context.TODO(),
Expand All @@ -205,7 +216,8 @@ func TestGRPCServer_GetNamespace(t *testing.T) {

t.Run("should return error Internal if NewStruct conversion failed", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
credentials["bar"] = string([]byte{0xff})
dummyResult := &namespace.Namespace{
ID: 1,
Expand Down Expand Up @@ -252,7 +264,8 @@ func TestGRPCServer_UpdateNamespace(t *testing.T) {

t.Run("should update a namespace", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Update(mock.AnythingOfType("context.todoCtx"), payload).Run(func(_a0 context.Context, _a1 *namespace.Namespace) {
_a1.ID = payload.ID
}).Return(nil).Once()
Expand All @@ -264,7 +277,8 @@ func TestGRPCServer_UpdateNamespace(t *testing.T) {

t.Run("should return error Invalid Argument if namespace service return err invalid", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Update(mock.AnythingOfType("context.todoCtx"), payload).Return(errors.ErrInvalid).Once()

res, err := dummyGRPCServer.UpdateNamespace(context.TODO(), request)
Expand All @@ -274,7 +288,8 @@ func TestGRPCServer_UpdateNamespace(t *testing.T) {

t.Run("should return error AlreadyExists if namespace service return err conflict", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Update(mock.AnythingOfType("context.todoCtx"), payload).Return(errors.ErrConflict).Once()

res, err := dummyGRPCServer.UpdateNamespace(context.TODO(), request)
Expand All @@ -285,7 +300,8 @@ func TestGRPCServer_UpdateNamespace(t *testing.T) {
t.Run("should return error Internal if updating namespaces failed", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}

dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Update(mock.AnythingOfType("context.todoCtx"), payload).Return(errors.New("random error")).Once()

res, err := dummyGRPCServer.UpdateNamespace(context.TODO(), request)
Expand All @@ -303,7 +319,8 @@ func TestGRPCServer_DeleteNamespace(t *testing.T) {

t.Run("should delete namespace object", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Delete(mock.AnythingOfType("context.todoCtx"), namespaceId).Return(nil).Once()
res, err := dummyGRPCServer.DeleteNamespace(context.TODO(), dummyReq)
assert.Nil(t, err)
Expand All @@ -313,7 +330,8 @@ func TestGRPCServer_DeleteNamespace(t *testing.T) {

t.Run("should return error Internal if deleting namespace failed", func(t *testing.T) {
mockedNamespaceService := &mocks.NamespaceService{}
dummyGRPCServer := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
dummyGRPCServer, err := v1beta1.NewGRPCServer(log.NewNoop(), api.HeadersConfig{}, &api.Deps{NamespaceService: mockedNamespaceService})
require.NoError(t, err)
mockedNamespaceService.EXPECT().Delete(mock.AnythingOfType("context.todoCtx"), namespaceId).Return(errors.New("random error")).Once()
res, err := dummyGRPCServer.DeleteNamespace(context.TODO(), dummyReq)
assert.Nil(t, res)
Expand Down
13 changes: 13 additions & 0 deletions internal/api/v1beta1/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/goto/siren/internal/api"
"github.com/goto/siren/pkg/errors"
sirenv1beta1 "github.com/goto/siren/proto/gotocompany/siren/v1beta1"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
Expand Down Expand Up @@ -67,6 +69,13 @@ func (s *GRPCServer) PostNotification(ctx context.Context, req *sirenv1beta1.Pos
notificationTemplate = req.GetTemplate()
}

s.metricNotificationReceiverSelectorCount.Record(
ctx, int64(len(receiverSelectors)),
metric.WithAttributes(
attribute.String("template", notificationTemplate),
),
)

notificationIDs, err := s.notificationService.Dispatch(ctx, []notification.Notification{
{
Type: notification.TypeEvent,
Expand Down Expand Up @@ -138,6 +147,10 @@ func (s *GRPCServer) PostBulkNotifications(ctx context.Context, req *sirenv1beta
})
}

s.metricBulkNotificationsCount.Record(
ctx, int64(len(notifications)),
)

notificationIDs, err := s.notificationService.Dispatch(ctx, notifications)
if err != nil {
if errors.Is(err, notification.ErrNoMessage) {
Expand Down
Loading

0 comments on commit 5371b23

Please sign in to comment.