NTP-4943 | making ee monitoring service webhook call upon severity update (#461)

* NTP-4943 | making ee monitoring service webhook call upon severity update

* NTP-4943 | review comments resolved
This commit is contained in:
Shashank Shekhar
2024-11-12 12:26:00 +05:30
committed by GitHub
parent 7f75b463b1
commit 85a1c89ca6
7 changed files with 294 additions and 3 deletions

27
common/util/go_util.go Normal file
View File

@@ -0,0 +1,27 @@
package util
import (
"context"
"errors"
"time"
)
var ErrTimeout = errors.New("task timed out")
func RunWithTimeout(timeout time.Duration, task func(ctx context.Context) error) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
done := make(chan error, 1)
go func() {
done <- task(ctx)
}()
select {
case err := <-done:
return err
case <-ctx.Done():
return ErrTimeout
}
}

View File

@@ -30,5 +30,4 @@ TEAM_NAME_MAX_LENGTH=100
#incidents
INCIDENTS_SHOW_LIMIT=10
GOOGLE_AUTH_KEY_CONTENT={"type":"service_account","project_id":"houston-402208","private_key_id":"private-key","private_key":"key","client_email":"houston@houston-402208.iam.gserviceaccount.com","client_id":"104926879839085398481","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/houston%40houston-402208.iam.gserviceaccount.com","universe_domain":"googleapis.com"}
GOOGLE_AUTH_EMAIL='houston@navi.com'
GOOGLE_AUTH_EMAIL='houston@navi.com'

View File

@@ -0,0 +1,24 @@
package contracts
type SeverityUpdateWebhookRequest struct {
Requester string `json:"requester"`
RequestType string `json:"requestType"`
IncidentDetails IncidentDetails `json:"incidentDetails"`
RequestMetaData SeverityUpdateRequestMetaData `json:"requestMetaData"`
}
type IncidentDetails struct {
IncidentType string `json:"incidentType"`
IncidentID uint `json:"incidentId"`
}
type SeverityUpdateRequestMetaData struct {
OldSeverity SeverityInfo `json:"oldSeverity"`
NewSeverity SeverityInfo `json:"newSeverity"`
UpdatedBy string `json:"updatedBy"`
}
type SeverityInfo struct {
Name string `json:"name"`
ID uint `json:"id"`
}

View File

@@ -0,0 +1,75 @@
package monitoringService
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/spf13/viper"
"go.uber.org/zap"
"houston/contracts"
"houston/logger"
"houston/pkg/rest"
"net/http"
"time"
)
type EEMonitoringServiceClientImpl struct {
BaseURL string
Client rest.HttpRestClient
DefaultTimeout time.Duration
WebhookAPIPath string
}
func NewEEMonitoringServiceActionsImpl(client rest.HttpRestClient) *EEMonitoringServiceClientImpl {
return &EEMonitoringServiceClientImpl{
Client: client,
BaseURL: viper.GetString("EE_MONITORING_SERVICE_BASEURL"),
DefaultTimeout: viper.GetDuration("DEFAULT_EE_MONITORING_SERVICE_TIMEOUT"),
WebhookAPIPath: viper.GetString("EE_MONITORING_SERVICE_WEBHOOK_API_PATH"),
}
}
func (client *EEMonitoringServiceClientImpl) TriggerSeverityUpdateWebhook(
incidentId uint,
triggerredBy string,
requestId string,
oldSeverity, newSeverity contracts.SeverityInfo,
) error {
logger.Info(fmt.Sprintf("Triggering severity update webhook for requestId: %s", requestId))
fullURL := client.BaseURL + client.WebhookAPIPath
requestHeaders := map[string]string{
"Content-Type": "application/json",
"CORRELATION_ID": requestId,
}
incidentDetails := contracts.IncidentDetails{
IncidentType: "HOUSTON",
IncidentID: incidentId,
}
metaData := contracts.SeverityUpdateRequestMetaData{
OldSeverity: oldSeverity,
NewSeverity: newSeverity,
UpdatedBy: triggerredBy,
}
requestBody := contracts.SeverityUpdateWebhookRequest{
Requester: "HOUSTON",
RequestType: "HOUSTON_SEVERITY_UPDATE",
IncidentDetails: incidentDetails,
RequestMetaData: metaData,
}
payload, err := json.Marshal(requestBody)
if err != nil {
logger.Error("Error while marshalling request body", zap.Error(err))
return errors.New("error while marshalling request body")
}
response, err := client.Client.PostWithTimeout(fullURL, *bytes.NewBuffer(payload), requestHeaders,
client.DefaultTimeout, nil)
if err != nil || response.StatusCode != http.StatusOK {
logger.Error("Error while triggering webhook", zap.Error(err))
return errors.New("error while triggering webhook")
}
return nil
}

View File

@@ -0,0 +1,9 @@
package monitoringService
import "houston/contracts"
type EEMonitoringServiceClient interface {
TriggerSeverityUpdateWebhook(
incidentId uint, triggerredBy string, requestId string, oldSeverity, newSeverity contracts.SeverityInfo,
) error
}

View File

@@ -0,0 +1,121 @@
package monitoringService
import (
"bytes"
"encoding/json"
"errors"
"github.com/gojuno/minimock/v3"
"github.com/google/uuid"
"github.com/spf13/viper"
"github.com/stretchr/testify/suite"
"houston/common/util"
"houston/contracts"
"houston/logger"
"houston/mocks"
service "houston/service/response"
"net/http"
"testing"
)
type EEMonitoringServiceSuite struct {
suite.Suite
}
func (suite *EEMonitoringServiceSuite) SetupSuite() {
logger.InitLogger()
viper.Set("EE_MONITORING_SERVICE_BASEURL", "http://localhost:8082")
viper.Set("DEFAULT_EE_MONITORING_SERVICE_TIMEOUT", "5s")
viper.Set("EE_MONITORING_SERVICE_WEBHOOK_API_PATH", "/monitoring-webhook/trigger")
viper.Set("DEFAULT_HTTP_REQUEST_TIMEOUT", "5s")
}
func (suite *EEMonitoringServiceSuite) Test_TriggerWebhook_ErrorResponseCase() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
requestId := uuid.NewString()
restClient := mocks.NewHttpRestClientMock(controller)
// Mocking HTTP calls
defaultTimeOut := viper.GetDuration("DEFAULT_EE_MONITORING_SERVICE_TIMEOUT")
fullURL := viper.GetString("EE_MONITORING_SERVICE_BASEURL") + viper.GetString("EE_MONITORING_SERVICE_WEBHOOK_API_PATH")
payload, _ := json.Marshal(getRequestBody())
requestHeaders := map[string]string{
"Content-Type": util.ContentTypeJSON,
"CORRELATION_ID": requestId,
}
restClient.PostWithTimeoutMock.When(fullURL, *bytes.NewBuffer(payload), requestHeaders, defaultTimeOut,
nil).Then(nil, errors.New("error while getting grafana images"))
responseChannel := make(chan service.MonitoringServiceClientResponse)
defer close(responseChannel)
err := NewEEMonitoringServiceActionsImpl(restClient).TriggerSeverityUpdateWebhook(
1, "test", requestId, getOldSeverity(), getNewSeverity(),
)
suite.Error(err)
}
func (suite *EEMonitoringServiceSuite) Test_TriggerWebhook_SuccessCase() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
requestId := uuid.NewString()
restClient := mocks.NewHttpRestClientMock(controller)
// Mocking HTTP calls
defaultTimeOut := viper.GetDuration("DEFAULT_EE_MONITORING_SERVICE_TIMEOUT")
fullURL := viper.GetString("EE_MONITORING_SERVICE_BASEURL") + viper.GetString("EE_MONITORING_SERVICE_WEBHOOK_API_PATH")
payload, _ := json.Marshal(getRequestBody())
requestHeaders := map[string]string{
"Content-Type": util.ContentTypeJSON,
"CORRELATION_ID": requestId,
}
response := &http.Response{
StatusCode: 200,
Body: nil,
}
restClient.PostWithTimeoutMock.When(fullURL, *bytes.NewBuffer(payload), requestHeaders, defaultTimeOut,
nil).Then(response, nil)
responseChannel := make(chan service.MonitoringServiceClientResponse)
defer close(responseChannel)
err := NewEEMonitoringServiceActionsImpl(restClient).TriggerSeverityUpdateWebhook(
1, "test", requestId, getOldSeverity(), getNewSeverity(),
)
suite.Nil(err)
}
func getRequestBody() *contracts.SeverityUpdateWebhookRequest {
incidentDetails := contracts.IncidentDetails{
IncidentType: "HOUSTON",
IncidentID: 1,
}
metaData := contracts.SeverityUpdateRequestMetaData{
OldSeverity: getOldSeverity(),
NewSeverity: getNewSeverity(),
UpdatedBy: "test",
}
return &contracts.SeverityUpdateWebhookRequest{
Requester: "HOUSTON",
RequestType: "HOUSTON_SEVERITY_UPDATE",
IncidentDetails: incidentDetails,
RequestMetaData: metaData,
}
}
func getOldSeverity() contracts.SeverityInfo {
return contracts.SeverityInfo{Name: "Sev-1", ID: 2}
}
func getNewSeverity() contracts.SeverityInfo {
return contracts.SeverityInfo{Name: "Sev-0", ID: 1}
}
func TestEEMonitoringServiceClient(t *testing.T) {
suite.Run(t, new(EEMonitoringServiceSuite))
}

View File

@@ -1,9 +1,11 @@
package impl
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
slackClient "github.com/slack-go/slack"
slackUtil "github.com/slack-go/slack"
"github.com/spf13/viper"
@@ -16,6 +18,7 @@ import (
"houston/common/util/env"
houstonSlackUtil "houston/common/util/slack"
stringUtil "houston/common/util/string"
"houston/contracts"
"houston/internal/processor/action/view"
"houston/logger"
"houston/model/incident"
@@ -32,6 +35,7 @@ import (
"houston/pkg/atlassian/dto/response"
"houston/pkg/conference"
"houston/pkg/google/googleDrive"
"houston/pkg/monitoringService"
"houston/pkg/rest"
"houston/repository/externalTeamRepo"
"houston/repository/incidentStatus"
@@ -104,6 +108,7 @@ type IncidentServiceV2 struct {
incidentTeamService incidentTeam.IncidentTeamService
incidentTeamTagValueService incidentTeamTagValue.IncidentTeamTagValueService
logService *logService.LogService
eeMonitoringServiceClient *monitoringService.EEMonitoringServiceClientImpl
}
/*
@@ -166,7 +171,8 @@ func NewIncidentServiceV2(db *gorm.DB) *IncidentServiceV2 {
incidentTeamTagValueService: incidentTeamTagValue.NewIncidentTeamTagValueService(
incidentTeamTagValueRepo.NewIncidentTeamTagValueRepository(db),
),
logService: logService.NewLogService(nil, db),
logService: logService.NewLogService(nil, db),
eeMonitoringServiceClient: monitoringService.NewEEMonitoringServiceActionsImpl(rest.NewHttpRestClient()),
}
driveActions, _ := googleDrive.NewGoogleDriveActions()
incidentService.rcaService = rcaServiceImpl.NewRcaService(
@@ -1422,6 +1428,8 @@ func (i *IncidentServiceV2) UpdateSeverityId(
return nil
}
oldSeverity := contracts.SeverityInfo{Name: incidentEntity.Severity.Name, ID: incidentEntity.SeverityId}
severityId, err := strconv.Atoi(request.SeverityId)
if err != nil {
logger.Error("String conversion to int failed in UpdateSeverityId for "+request.SeverityId, zap.Error(err))
@@ -1514,11 +1522,39 @@ func (i *IncidentServiceV2) UpdateSeverityId(
}()
}
go i.SendAlert(incidentEntity)
i.eeMonitoringServiceWebhookCall(incidentEntity, oldSeverity)
}
}
return nil
}
func (i *IncidentServiceV2) eeMonitoringServiceWebhookCall(
incidentEntity *incident.IncidentEntity, oldSeverity contracts.SeverityInfo,
) {
task := func(ctx context.Context) error {
entities, err := i.incidentRepository.GetIncidentsByIds([]uint{incidentEntity.ID})
if err != nil {
return errors.New(fmt.Sprintf("error in fetching incident by id: %+v", err))
}
updatedIncident := entities[0]
newSeverity := contracts.SeverityInfo{Name: updatedIncident.Severity.Name, ID: updatedIncident.SeverityId}
err = i.eeMonitoringServiceClient.TriggerSeverityUpdateWebhook(
incidentEntity.ID, incidentEntity.UpdatedBy, uuid.NewString(), oldSeverity, newSeverity,
)
if err != nil {
return errors.New(fmt.Sprintf("error in triggering severity update webhook: %+v", err))
}
return nil
}
err := util.RunWithTimeout(30*time.Second, task)
if err != nil {
logger.Error(fmt.Sprintf("%s Failed to trigger webhook: %v", updateLogTag, err))
} else {
logger.Info(fmt.Sprintf("%s Severity update webhook triggered successfully", updateLogTag))
}
}
func isValidRequestForUpdateSeverity(teamSeverityUpdateRule team.TeamSeverityUpdateRule, reportingTeam team.TeamEntity, userDTO *user.UserDTO) bool {
strategy := teamSeverityUpdateRule.Strategy
allowedUserIds := teamSeverityUpdateRule.AllowedUserIdsForSeverityChange