diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index f3884127b5..fd4ecf4360 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -39,6 +39,7 @@ type APIHandler struct { basePath string apiPrefix string reader *Reader + alertManager am.Manager relationalDB *interfaces.ModelDao ready func(http.HandlerFunc) http.HandlerFunc } @@ -46,9 +47,11 @@ type APIHandler struct { // NewAPIHandler returns an APIHandler func NewAPIHandler(reader *Reader, relationalDB *interfaces.ModelDao) (*APIHandler, error) { + alertManager := am.New("") aH := &APIHandler{ reader: reader, relationalDB: relationalDB, + alertManager: alertManager, } aH.ready = aH.testReady @@ -172,6 +175,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/channels/{id}", aH.editChannel).Methods(http.MethodPut) router.HandleFunc("/api/v1/channels/{id}", aH.deleteChannel).Methods(http.MethodDelete) router.HandleFunc("/api/v1/channels", aH.createChannel).Methods(http.MethodPost) + router.HandleFunc("/api/v1/testChannel", aH.testChannel).Methods(http.MethodPost) router.HandleFunc("/api/v1/rules", aH.listRulesFromProm).Methods(http.MethodGet) router.HandleFunc("/api/v1/rules/{id}", aH.getRule).Methods(http.MethodGet) router.HandleFunc("/api/v1/rules", aH.createRule).Methods(http.MethodPost) @@ -451,6 +455,33 @@ func (aH *APIHandler) listChannels(w http.ResponseWriter, r *http.Request) { aH.respond(w, channels) } +// testChannels sends test alert to all registered channels +func (aH *APIHandler) testChannel(w http.ResponseWriter, r *http.Request) { + + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + zap.S().Errorf("Error in getting req body of testChannel API\n", err) + aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + receiver := &am.Receiver{} + if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer + zap.S().Errorf("Error in parsing req body of testChannel API\n", err) + aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + // send alert + apiErrorObj := aH.alertManager.TestReceiver(receiver) + if apiErrorObj != nil { + aH.respondError(w, apiErrorObj, nil) + return + } + aH.respond(w, "test alert sent") +} + func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] diff --git a/pkg/query-service/integrations/alertManager/manager.go b/pkg/query-service/integrations/alertManager/manager.go index f0e8b024d1..47dc96f366 100644 --- a/pkg/query-service/integrations/alertManager/manager.go +++ b/pkg/query-service/integrations/alertManager/manager.go @@ -2,13 +2,14 @@ package alertManager // Wrapper to connect and process alert manager functions import ( - "fmt" - "encoding/json" "bytes" + "encoding/json" + "fmt" "net/http" - "go.uber.org/zap" + "go.signoz.io/query-service/constants" "go.signoz.io/query-service/model" + "go.uber.org/zap" ) const contentType = "application/json" @@ -17,16 +18,17 @@ type Manager interface { AddRoute(receiver *Receiver) *model.ApiError EditRoute(receiver *Receiver) *model.ApiError DeleteRoute(name string) *model.ApiError + TestReceiver(receiver *Receiver) *model.ApiError } -func New(url string) Manager{ - - if url == ""{ +func New(url string) Manager { + + if url == "" { url = constants.GetAlertManagerApiPrefix() } - return &manager { - url: url, + return &manager{ + url: url, } } @@ -34,11 +36,10 @@ type manager struct { url string } - func prepareAmChannelApiURL() string { basePath := constants.GetAlertManagerApiPrefix() AmChannelApiPath := constants.AmChannelApiPath - + if len(AmChannelApiPath) > 0 && rune(AmChannelApiPath[0]) == rune('/') { AmChannelApiPath = AmChannelApiPath[1:] } @@ -46,13 +47,18 @@ func prepareAmChannelApiURL() string { return fmt.Sprintf("%s%s", basePath, AmChannelApiPath) } -func (m *manager) AddRoute(receiver *Receiver) (*model.ApiError) { - +func prepareTestApiURL() string { + basePath := constants.GetAlertManagerApiPrefix() + return fmt.Sprintf("%s%s", basePath, "v1/testReceiver") +} + +func (m *manager) AddRoute(receiver *Receiver) *model.ApiError { + receiverString, _ := json.Marshal(receiver) amURL := prepareAmChannelApiURL() response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString)) - + if err != nil { zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amURL), err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -81,7 +87,7 @@ func (m *manager) EditRoute(receiver *Receiver) *model.ApiError { client := &http.Client{} response, err := client.Do(req) - + if err != nil { zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(PUT %s)\n", amURL), err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -125,5 +131,29 @@ func (m *manager) DeleteRoute(name string) *model.ApiError { return nil } +func (m *manager) TestReceiver(receiver *Receiver) *model.ApiError { + receiverBytes, _ := json.Marshal(receiver) + amTestURL := prepareTestApiURL() + response, err := http.Post(amTestURL, contentType, bytes.NewBuffer(receiverBytes)) + + if err != nil { + zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amTestURL), err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if response.StatusCode > 201 && response.StatusCode < 400 { + err := fmt.Errorf(fmt.Sprintf("Invalid parameters in test alert api for alertmanager(POST %s)\n", amTestURL), response.Status) + zap.S().Error(err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if response.StatusCode > 400 { + err := fmt.Errorf(fmt.Sprintf("Received Server Error response for API call to alertmanager(POST %s)\n", amTestURL), response.Status) + zap.S().Error(err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + return nil +}