chore: move channel management from CH reader to rule DB (#5934)

This commit is contained in:
Srikanth Chekuri 2024-09-17 11:41:46 +05:30 committed by GitHub
parent b78ade2cf2
commit f8e97c9c5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 362 additions and 370 deletions

View File

@ -1,15 +1,12 @@
package clickhouseReader package clickhouseReader
import ( import (
"bytes"
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"math" "math"
"math/rand" "math/rand"
"net/http"
"os" "os"
"reflect" "reflect"
"regexp" "regexp"
@ -173,7 +170,7 @@ func NewReaderFromClickhouseConnection(
cluster string, cluster string,
useLogsNewSchema bool, useLogsNewSchema bool,
) *ClickHouseReader { ) *ClickHouseReader {
alertManager, err := am.New("") alertManager, err := am.New()
if err != nil { if err != nil {
zap.L().Error("failed to initialize alert manager", zap.Error(err)) zap.L().Error("failed to initialize alert manager", zap.Error(err))
zap.L().Error("check if the alert manager URL is correctly set and valid") zap.L().Error("check if the alert manager URL is correctly set and valid")
@ -414,267 +411,6 @@ func (r *ClickHouseReader) GetConn() clickhouse.Conn {
return r.db return r.db
} }
func (r *ClickHouseReader) LoadChannel(channel *model.ChannelItem) *model.ApiError {
receiver := &am.Receiver{}
if err := json.Unmarshal([]byte(channel.Data), receiver); err != nil { // Parse []byte to go struct pointer
return &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
response, err := http.Post(constants.GetAlertManagerApiPrefix()+"v1/receivers", "application/json", bytes.NewBuffer([]byte(channel.Data)))
if err != nil {
zap.L().Error("Error in getting response of API call to alertmanager/v1/receivers", zap.Error(err))
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if response.StatusCode > 299 {
responseData, _ := io.ReadAll(response.Body)
err := fmt.Errorf("error in getting 2xx response in API call to alertmanager/v1/receivers")
zap.L().Error("Error in getting 2xx response in API call to alertmanager/v1/receivers", zap.String("Status", response.Status), zap.String("Data", string(responseData)))
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (r *ClickHouseReader) GetChannel(id string) (*model.ChannelItem, *model.ApiError) {
idInt, _ := strconv.Atoi(id)
channel := model.ChannelItem{}
query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels WHERE id=? "
stmt, err := r.localDB.Preparex(query)
if err != nil {
zap.L().Error("Error in preparing sql query for GetChannel", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
err = stmt.Get(&channel, idInt)
if err != nil {
zap.L().Error("Error in getting channel with id", zap.Int("id", idInt), zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return &channel, nil
}
func (r *ClickHouseReader) DeleteChannel(id string) *model.ApiError {
idInt, _ := strconv.Atoi(id)
channelToDelete, apiErrorObj := r.GetChannel(id)
if apiErrorObj != nil {
return apiErrorObj
}
tx, err := r.localDB.Begin()
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
{
stmt, err := tx.Prepare(`DELETE FROM notification_channels WHERE id=$1;`)
if err != nil {
zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback()
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
defer stmt.Close()
if _, err := stmt.Exec(idInt); err != nil {
zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback() // return an error too, we may want to wrap them
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
apiError := r.alertManager.DeleteRoute(channelToDelete.Name)
if apiError != nil {
tx.Rollback()
return apiError
}
err = tx.Commit()
if err != nil {
zap.L().Error("Error in committing transaction for DELETE command to notification_channels", zap.Error(err))
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (r *ClickHouseReader) GetChannels() (*[]model.ChannelItem, *model.ApiError) {
channels := []model.ChannelItem{}
query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels"
err := r.localDB.Select(&channels, query)
zap.L().Info(query)
if err != nil {
zap.L().Error("Error in processing sql query", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return &channels, nil
}
func getChannelType(receiver *am.Receiver) string {
if receiver.EmailConfigs != nil {
return "email"
}
if receiver.OpsGenieConfigs != nil {
return "opsgenie"
}
if receiver.PagerdutyConfigs != nil {
return "pagerduty"
}
if receiver.PushoverConfigs != nil {
return "pushover"
}
if receiver.SNSConfigs != nil {
return "sns"
}
if receiver.SlackConfigs != nil {
return "slack"
}
if receiver.VictorOpsConfigs != nil {
return "victorops"
}
if receiver.WebhookConfigs != nil {
return "webhook"
}
if receiver.WechatConfigs != nil {
return "wechat"
}
if receiver.MSTeamsConfigs != nil {
return "msteams"
}
return ""
}
func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) {
idInt, _ := strconv.Atoi(id)
channel, apiErrObj := r.GetChannel(id)
if apiErrObj != nil {
return nil, apiErrObj
}
if channel.Name != receiver.Name {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("channel name cannot be changed")}
}
tx, err := r.localDB.Begin()
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
channel_type := getChannelType(receiver)
// check if channel type is supported in the current user plan
if err := r.featureFlags.CheckFeature(fmt.Sprintf("ALERT_CHANNEL_%s", strings.ToUpper(channel_type))); err != nil {
zap.L().Warn("an unsupported feature was blocked", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unsupported feature. please upgrade your plan to access this feature")}
}
receiverString, _ := json.Marshal(receiver)
{
stmt, err := tx.Prepare(`UPDATE notification_channels SET updated_at=$1, type=$2, data=$3 WHERE id=$4;`)
if err != nil {
zap.L().Error("Error in preparing statement for UPDATE to notification_channels", zap.Error(err))
tx.Rollback()
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
defer stmt.Close()
if _, err := stmt.Exec(time.Now(), channel_type, string(receiverString), idInt); err != nil {
zap.L().Error("Error in Executing prepared statement for UPDATE to notification_channels", zap.Error(err))
tx.Rollback() // return an error too, we may want to wrap them
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
apiError := r.alertManager.EditRoute(receiver)
if apiError != nil {
tx.Rollback()
return nil, apiError
}
err = tx.Commit()
if err != nil {
zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return receiver, nil
}
func (r *ClickHouseReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
channel_type := getChannelType(receiver)
// check if channel type is supported in the current user plan
if err := r.featureFlags.CheckFeature(fmt.Sprintf("ALERT_CHANNEL_%s", strings.ToUpper(channel_type))); err != nil {
zap.L().Warn("an unsupported feature was blocked", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unsupported feature. please upgrade your plan to access this feature")}
}
receiverString, _ := json.Marshal(receiver)
tx, err := r.localDB.Begin()
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
{
stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
if err != nil {
zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback()
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
defer stmt.Close()
if _, err := stmt.Exec(time.Now(), time.Now(), receiver.Name, channel_type, string(receiverString)); err != nil {
zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback() // return an error too, we may want to wrap them
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
apiError := r.alertManager.AddRoute(receiver)
if apiError != nil {
tx.Rollback()
return nil, apiError
}
err = tx.Commit()
if err != nil {
zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return receiver, nil
}
func (r *ClickHouseReader) GetInstantQueryMetricsResult(ctx context.Context, queryParams *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) { func (r *ClickHouseReader) GetInstantQueryMetricsResult(ctx context.Context, queryParams *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) {
qry, err := r.queryEngine.NewInstantQuery(ctx, r.remoteStorage, nil, queryParams.Query, queryParams.Time) qry, err := r.queryEngine.NewInstantQuery(ctx, r.remoteStorage, nil, queryParams.Query, queryParams.Time)
if err != nil { if err != nil {

View File

@ -151,7 +151,7 @@ type APIHandlerOpts struct {
// NewAPIHandler returns an APIHandler // NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) { func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
alertManager, err := am.New("") alertManager, err := am.New()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1182,7 +1182,7 @@ func (aH *APIHandler) editRule(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getChannel(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getChannel(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
channel, apiErrorObj := aH.reader.GetChannel(id) channel, apiErrorObj := aH.ruleManager.RuleDB().GetChannel(id)
if apiErrorObj != nil { if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)
return return
@ -1192,7 +1192,7 @@ func (aH *APIHandler) getChannel(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
apiErrorObj := aH.reader.DeleteChannel(id) apiErrorObj := aH.ruleManager.RuleDB().DeleteChannel(id)
if apiErrorObj != nil { if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)
return return
@ -1201,7 +1201,7 @@ func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) {
} }
func (aH *APIHandler) listChannels(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) listChannels(w http.ResponseWriter, r *http.Request) {
channels, apiErrorObj := aH.reader.GetChannels() channels, apiErrorObj := aH.ruleManager.RuleDB().GetChannels()
if apiErrorObj != nil { if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)
return return
@ -1254,7 +1254,7 @@ func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) {
return return
} }
_, apiErrorObj := aH.reader.EditChannel(receiver, id) _, apiErrorObj := aH.ruleManager.RuleDB().EditChannel(receiver, id)
if apiErrorObj != nil { if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)
@ -1282,7 +1282,7 @@ func (aH *APIHandler) createChannel(w http.ResponseWriter, r *http.Request) {
return return
} }
_, apiErrorObj := aH.reader.CreateChannel(receiver) _, apiErrorObj := aH.ruleManager.RuleDB().CreateChannel(receiver)
if apiErrorObj != nil { if apiErrorObj != nil {
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)

View File

@ -24,42 +24,62 @@ type Manager interface {
TestReceiver(receiver *Receiver) *model.ApiError TestReceiver(receiver *Receiver) *model.ApiError
} }
func New(url string) (Manager, error) { func defaultOptions() []ManagerOptions {
return []ManagerOptions{
WithURL(constants.GetAlertManagerApiPrefix()),
WithChannelApiPath(constants.AmChannelApiPath),
}
}
if url == "" { type ManagerOptions func(m *manager) error
url = constants.GetAlertManagerApiPrefix()
func New(opts ...ManagerOptions) (Manager, error) {
m := &manager{}
newOpts := defaultOptions()
newOpts = append(newOpts, opts...)
for _, opt := range newOpts {
err := opt(m)
if err != nil {
return nil, err
}
} }
urlParsed, err := neturl.Parse(url) return m, nil
if err != nil { }
return nil, err
}
return &manager{ func WithURL(url string) ManagerOptions {
url: url, return func(m *manager) error {
parsedURL: urlParsed, m.url = url
}, nil parsedURL, err := neturl.Parse(url)
if err != nil {
return err
}
m.parsedURL = parsedURL
return nil
}
}
func WithChannelApiPath(path string) ManagerOptions {
return func(m *manager) error {
m.channelApiPath = path
return nil
}
} }
type manager struct { type manager struct {
url string url string
parsedURL *neturl.URL parsedURL *neturl.URL
channelApiPath string
} }
func prepareAmChannelApiURL() string { func (m *manager) prepareAmChannelApiURL() string {
basePath := constants.GetAlertManagerApiPrefix() return fmt.Sprintf("%s%s", m.url, m.channelApiPath)
AmChannelApiPath := constants.AmChannelApiPath
if len(AmChannelApiPath) > 0 && rune(AmChannelApiPath[0]) == rune('/') {
AmChannelApiPath = AmChannelApiPath[1:]
}
return fmt.Sprintf("%s%s", basePath, AmChannelApiPath)
} }
func prepareTestApiURL() string { func (m *manager) prepareTestApiURL() string {
basePath := constants.GetAlertManagerApiPrefix() return fmt.Sprintf("%s%s", m.url, "v1/testReceiver")
return fmt.Sprintf("%s%s", basePath, "v1/testReceiver")
} }
func (m *manager) URL() *neturl.URL { func (m *manager) URL() *neturl.URL {
@ -79,7 +99,7 @@ func (m *manager) AddRoute(receiver *Receiver) *model.ApiError {
receiverString, _ := json.Marshal(receiver) receiverString, _ := json.Marshal(receiver)
amURL := prepareAmChannelApiURL() amURL := m.prepareAmChannelApiURL()
response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString)) response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString))
if err != nil { if err != nil {
@ -97,7 +117,7 @@ func (m *manager) AddRoute(receiver *Receiver) *model.ApiError {
func (m *manager) EditRoute(receiver *Receiver) *model.ApiError { func (m *manager) EditRoute(receiver *Receiver) *model.ApiError {
receiverString, _ := json.Marshal(receiver) receiverString, _ := json.Marshal(receiver)
amURL := prepareAmChannelApiURL() amURL := m.prepareAmChannelApiURL()
req, err := http.NewRequest(http.MethodPut, amURL, bytes.NewBuffer(receiverString)) req, err := http.NewRequest(http.MethodPut, amURL, bytes.NewBuffer(receiverString))
if err != nil { if err != nil {
@ -126,7 +146,7 @@ func (m *manager) DeleteRoute(name string) *model.ApiError {
values := map[string]string{"name": name} values := map[string]string{"name": name}
requestData, _ := json.Marshal(values) requestData, _ := json.Marshal(values)
amURL := prepareAmChannelApiURL() amURL := m.prepareAmChannelApiURL()
req, err := http.NewRequest(http.MethodDelete, amURL, bytes.NewBuffer(requestData)) req, err := http.NewRequest(http.MethodDelete, amURL, bytes.NewBuffer(requestData))
if err != nil { if err != nil {
@ -156,7 +176,7 @@ func (m *manager) TestReceiver(receiver *Receiver) *model.ApiError {
receiverBytes, _ := json.Marshal(receiver) receiverBytes, _ := json.Marshal(receiver)
amTestURL := prepareTestApiURL() amTestURL := m.prepareTestApiURL()
response, err := http.Post(amTestURL, contentType, bytes.NewBuffer(receiverBytes)) response, err := http.Post(amTestURL, contentType, bytes.NewBuffer(receiverBytes))
if err != nil { if err != nil {

View File

@ -295,7 +295,7 @@ func newAlertmanagerSet(urls []string, timeout time.Duration, logger log.Logger)
ams := []Manager{} ams := []Manager{}
for _, u := range urls { for _, u := range urls {
am, err := New(u) am, err := New(WithURL(u))
if err != nil { if err != nil {
level.Error(s.logger).Log(fmt.Sprintf("invalid alert manager url %s: %s", u, err)) level.Error(s.logger).Log(fmt.Sprintf("invalid alert manager url %s: %s", u, err))
} else { } else {

View File

@ -8,18 +8,11 @@ import (
"github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/stats" "github.com/prometheus/prometheus/util/stats"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
) )
type Reader interface { type Reader interface {
GetChannel(id string) (*model.ChannelItem, *model.ApiError)
GetChannels() (*[]model.ChannelItem, *model.ApiError)
DeleteChannel(id string) *model.ApiError
CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError)
EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError)
GetInstantQueryMetricsResult(ctx context.Context, query *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) GetInstantQueryMetricsResult(ctx context.Context, query *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError)
GetQueryRangeResult(ctx context.Context, query *model.QueryRangeParams) (*promql.Result, *stats.QueryStats, *model.ApiError) GetQueryRangeResult(ctx context.Context, query *model.QueryRangeParams) (*promql.Result, *stats.QueryStats, *model.ApiError)
GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams, skipConfig *model.SkipConfig) (*[]model.ServiceOverviewItem, *model.ApiError) GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams, skipConfig *model.SkipConfig) (*[]model.ServiceOverviewItem, *model.ApiError)

View File

@ -618,6 +618,7 @@ type AlertsInfo struct {
LogsBasedAlerts int `json:"logsBasedAlerts"` LogsBasedAlerts int `json:"logsBasedAlerts"`
MetricBasedAlerts int `json:"metricBasedAlerts"` MetricBasedAlerts int `json:"metricBasedAlerts"`
TracesBasedAlerts int `json:"tracesBasedAlerts"` TracesBasedAlerts int `json:"tracesBasedAlerts"`
TotalChannels int `json:"totalChannels"`
SlackChannels int `json:"slackChannels"` SlackChannels int `json:"slackChannels"`
WebHookChannels int `json:"webHookChannels"` WebHookChannels int `json:"webHookChannels"`
PagerDutyChannels int `json:"pagerDutyChannels"` PagerDutyChannels int `json:"pagerDutyChannels"`

View File

@ -11,6 +11,7 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/common" "go.signoz.io/signoz/pkg/query-service/common"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.uber.org/zap" "go.uber.org/zap"
@ -18,6 +19,12 @@ import (
// Data store to capture user alert rule settings // Data store to capture user alert rule settings
type RuleDB interface { type RuleDB interface {
GetChannel(id string) (*model.ChannelItem, *model.ApiError)
GetChannels() (*[]model.ChannelItem, *model.ApiError)
DeleteChannel(id string) *model.ApiError
CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError)
EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError)
// CreateRuleTx stores rule in the db and returns tx and group name (on success) // CreateRuleTx stores rule in the db and returns tx and group name (on success)
CreateRuleTx(ctx context.Context, rule string) (int64, Tx, error) CreateRuleTx(ctx context.Context, rule string) (int64, Tx, error)
@ -68,13 +75,15 @@ type Tx interface {
type ruleDB struct { type ruleDB struct {
*sqlx.DB *sqlx.DB
alertManager am.Manager
} }
// todo: move init methods for creating tables // todo: move init methods for creating tables
func NewRuleDB(db *sqlx.DB) RuleDB { func NewRuleDB(db *sqlx.DB, alertManager am.Manager) RuleDB {
return &ruleDB{ return &ruleDB{
db, db,
alertManager,
} }
} }
@ -303,6 +312,229 @@ func (r *ruleDB) EditPlannedMaintenance(ctx context.Context, maintenance Planned
return "", nil return "", nil
} }
func getChannelType(receiver *am.Receiver) string {
if receiver.EmailConfigs != nil {
return "email"
}
if receiver.OpsGenieConfigs != nil {
return "opsgenie"
}
if receiver.PagerdutyConfigs != nil {
return "pagerduty"
}
if receiver.PushoverConfigs != nil {
return "pushover"
}
if receiver.SNSConfigs != nil {
return "sns"
}
if receiver.SlackConfigs != nil {
return "slack"
}
if receiver.VictorOpsConfigs != nil {
return "victorops"
}
if receiver.WebhookConfigs != nil {
return "webhook"
}
if receiver.WechatConfigs != nil {
return "wechat"
}
if receiver.MSTeamsConfigs != nil {
return "msteams"
}
return ""
}
func (r *ruleDB) GetChannel(id string) (*model.ChannelItem, *model.ApiError) {
idInt, _ := strconv.Atoi(id)
channel := model.ChannelItem{}
query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels WHERE id=?;"
stmt, err := r.Preparex(query)
if err != nil {
zap.L().Error("Error in preparing sql query for GetChannel", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
err = stmt.Get(&channel, idInt)
if err != nil {
zap.L().Error("Error in getting channel with id", zap.Int("id", idInt), zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return &channel, nil
}
func (r *ruleDB) DeleteChannel(id string) *model.ApiError {
idInt, _ := strconv.Atoi(id)
channelToDelete, apiErrorObj := r.GetChannel(id)
if apiErrorObj != nil {
return apiErrorObj
}
tx, err := r.Begin()
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
{
stmt, err := tx.Prepare(`DELETE FROM notification_channels WHERE id=$1;`)
if err != nil {
zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback()
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
defer stmt.Close()
if _, err := stmt.Exec(idInt); err != nil {
zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback() // return an error too, we may want to wrap them
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
apiError := r.alertManager.DeleteRoute(channelToDelete.Name)
if apiError != nil {
tx.Rollback()
return apiError
}
err = tx.Commit()
if err != nil {
zap.L().Error("Error in committing transaction for DELETE command to notification_channels", zap.Error(err))
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (r *ruleDB) GetChannels() (*[]model.ChannelItem, *model.ApiError) {
channels := []model.ChannelItem{}
query := "SELECT id, created_at, updated_at, name, type, data data FROM notification_channels"
err := r.Select(&channels, query)
zap.L().Info(query)
if err != nil {
zap.L().Error("Error in processing sql query", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return &channels, nil
}
func (r *ruleDB) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) {
idInt, _ := strconv.Atoi(id)
channel, apiErrObj := r.GetChannel(id)
if apiErrObj != nil {
return nil, apiErrObj
}
if channel.Name != receiver.Name {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("channel name cannot be changed")}
}
tx, err := r.Begin()
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
channel_type := getChannelType(receiver)
receiverString, _ := json.Marshal(receiver)
{
stmt, err := tx.Prepare(`UPDATE notification_channels SET updated_at=$1, type=$2, data=$3 WHERE id=$4;`)
if err != nil {
zap.L().Error("Error in preparing statement for UPDATE to notification_channels", zap.Error(err))
tx.Rollback()
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
defer stmt.Close()
if _, err := stmt.Exec(time.Now(), channel_type, string(receiverString), idInt); err != nil {
zap.L().Error("Error in Executing prepared statement for UPDATE to notification_channels", zap.Error(err))
tx.Rollback() // return an error too, we may want to wrap them
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
apiError := r.alertManager.EditRoute(receiver)
if apiError != nil {
tx.Rollback()
return nil, apiError
}
err = tx.Commit()
if err != nil {
zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return receiver, nil
}
func (r *ruleDB) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
channel_type := getChannelType(receiver)
receiverString, _ := json.Marshal(receiver)
tx, err := r.Begin()
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
{
stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
if err != nil {
zap.L().Error("Error in preparing statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback()
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
defer stmt.Close()
if _, err := stmt.Exec(time.Now(), time.Now(), receiver.Name, channel_type, string(receiverString)); err != nil {
zap.L().Error("Error in Executing prepared statement for INSERT to notification_channels", zap.Error(err))
tx.Rollback() // return an error too, we may want to wrap them
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
apiError := r.alertManager.AddRoute(receiver)
if apiError != nil {
tx.Rollback()
return nil, apiError
}
err = tx.Commit()
if err != nil {
zap.L().Error("Error in committing transaction for INSERT to notification_channels", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return receiver, nil
}
func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) { func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) {
alertsInfo := model.AlertsInfo{} alertsInfo := model.AlertsInfo{}
// fetch alerts from rules db // fetch alerts from rules db
@ -353,5 +585,31 @@ func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) {
alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1 alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1
} }
alertsInfo.AlertNames = alertNames alertsInfo.AlertNames = alertNames
channels, _ := r.GetChannels()
if channels != nil {
alertsInfo.TotalChannels = len(*channels)
for _, channel := range *channels {
if channel.Type == "slack" {
alertsInfo.SlackChannels = alertsInfo.SlackChannels + 1
}
if channel.Type == "webhook" {
alertsInfo.WebHookChannels = alertsInfo.WebHookChannels + 1
}
if channel.Type == "email" {
alertsInfo.EmailChannels = alertsInfo.EmailChannels + 1
}
if channel.Type == "pagerduty" {
alertsInfo.PagerDutyChannels = alertsInfo.PagerDutyChannels + 1
}
if channel.Type == "opsgenie" {
alertsInfo.OpsGenieChannels = alertsInfo.OpsGenieChannels + 1
}
if channel.Type == "msteams" {
alertsInfo.MSTeamsChannels = alertsInfo.MSTeamsChannels + 1
}
}
}
return &alertsInfo, nil return &alertsInfo, nil
} }

View File

@ -190,7 +190,12 @@ func NewManager(o *ManagerOptions) (*Manager, error) {
return nil, err return nil, err
} }
db := NewRuleDB(o.DBConn) amManager, err := am.New()
if err != nil {
return nil, err
}
db := NewRuleDB(o.DBConn, amManager)
telemetry.GetInstance().SetAlertsInfoCallback(db.GetAlertsInfo) telemetry.GetInstance().SetAlertsInfoCallback(db.GetAlertsInfo)

View File

@ -325,65 +325,46 @@ func createTelemetry() {
if err == nil { if err == nil {
dashboardsInfo, err := telemetry.dashboardsInfoCallback(ctx) dashboardsInfo, err := telemetry.dashboardsInfoCallback(ctx)
if err == nil { if err == nil {
channels, err := telemetry.reader.GetChannels() savedViewsInfo, err := telemetry.savedViewsInfoCallback(ctx)
if err == nil { if err == nil {
for _, channel := range *channels { dashboardsAlertsData := map[string]interface{}{
switch channel.Type { "totalDashboards": dashboardsInfo.TotalDashboards,
case "slack": "totalDashboardsWithPanelAndName": dashboardsInfo.TotalDashboardsWithPanelAndName,
alertsInfo.SlackChannels++ "dashboardNames": dashboardsInfo.DashboardNames,
case "webhook": "alertNames": alertsInfo.AlertNames,
alertsInfo.WebHookChannels++ "logsBasedPanels": dashboardsInfo.LogsBasedPanels,
case "pagerduty": "metricBasedPanels": dashboardsInfo.MetricBasedPanels,
alertsInfo.PagerDutyChannels++ "tracesBasedPanels": dashboardsInfo.TracesBasedPanels,
case "opsgenie": "dashboardsWithTSV2": dashboardsInfo.QueriesWithTSV2,
alertsInfo.OpsGenieChannels++ "dashboardWithLogsChQuery": dashboardsInfo.DashboardsWithLogsChQuery,
case "email": "totalAlerts": alertsInfo.TotalAlerts,
alertsInfo.EmailChannels++ "alertsWithTSV2": alertsInfo.AlertsWithTSV2,
case "msteams": "logsBasedAlerts": alertsInfo.LogsBasedAlerts,
alertsInfo.MSTeamsChannels++ "metricBasedAlerts": alertsInfo.MetricBasedAlerts,
} "tracesBasedAlerts": alertsInfo.TracesBasedAlerts,
"totalChannels": alertsInfo.TotalChannels,
"totalSavedViews": savedViewsInfo.TotalSavedViews,
"logsSavedViews": savedViewsInfo.LogsSavedViews,
"tracesSavedViews": savedViewsInfo.TracesSavedViews,
"slackChannels": alertsInfo.SlackChannels,
"webHookChannels": alertsInfo.WebHookChannels,
"pagerDutyChannels": alertsInfo.PagerDutyChannels,
"opsGenieChannels": alertsInfo.OpsGenieChannels,
"emailChannels": alertsInfo.EmailChannels,
"msteamsChannels": alertsInfo.MSTeamsChannels,
"metricsBuilderQueries": alertsInfo.MetricsBuilderQueries,
"metricsClickHouseQueries": alertsInfo.MetricsClickHouseQueries,
"metricsPrometheusQueries": alertsInfo.MetricsPrometheusQueries,
"spanMetricsPrometheusQueries": alertsInfo.SpanMetricsPrometheusQueries,
"alertsWithLogsChQuery": alertsInfo.AlertsWithLogsChQuery,
} }
savedViewsInfo, err := telemetry.savedViewsInfoCallback(ctx) // send event only if there are dashboards or alerts or channels
if err == nil { if (dashboardsInfo.TotalDashboards > 0 || alertsInfo.TotalAlerts > 0 || alertsInfo.TotalChannels > 0 || savedViewsInfo.TotalSavedViews > 0) && apiErr == nil {
dashboardsAlertsData := map[string]interface{}{ for _, user := range users {
"totalDashboards": dashboardsInfo.TotalDashboards, if user.Email == DEFAULT_CLOUD_EMAIL {
"totalDashboardsWithPanelAndName": dashboardsInfo.TotalDashboardsWithPanelAndName, continue
"dashboardNames": dashboardsInfo.DashboardNames,
"alertNames": alertsInfo.AlertNames,
"logsBasedPanels": dashboardsInfo.LogsBasedPanels,
"metricBasedPanels": dashboardsInfo.MetricBasedPanels,
"tracesBasedPanels": dashboardsInfo.TracesBasedPanels,
"dashboardsWithTSV2": dashboardsInfo.QueriesWithTSV2,
"dashboardWithLogsChQuery": dashboardsInfo.DashboardsWithLogsChQuery,
"totalAlerts": alertsInfo.TotalAlerts,
"alertsWithTSV2": alertsInfo.AlertsWithTSV2,
"logsBasedAlerts": alertsInfo.LogsBasedAlerts,
"metricBasedAlerts": alertsInfo.MetricBasedAlerts,
"tracesBasedAlerts": alertsInfo.TracesBasedAlerts,
"totalChannels": len(*channels),
"totalSavedViews": savedViewsInfo.TotalSavedViews,
"logsSavedViews": savedViewsInfo.LogsSavedViews,
"tracesSavedViews": savedViewsInfo.TracesSavedViews,
"slackChannels": alertsInfo.SlackChannels,
"webHookChannels": alertsInfo.WebHookChannels,
"pagerDutyChannels": alertsInfo.PagerDutyChannels,
"opsGenieChannels": alertsInfo.OpsGenieChannels,
"emailChannels": alertsInfo.EmailChannels,
"msteamsChannels": alertsInfo.MSTeamsChannels,
"metricsBuilderQueries": alertsInfo.MetricsBuilderQueries,
"metricsClickHouseQueries": alertsInfo.MetricsClickHouseQueries,
"metricsPrometheusQueries": alertsInfo.MetricsPrometheusQueries,
"spanMetricsPrometheusQueries": alertsInfo.SpanMetricsPrometheusQueries,
"alertsWithLogsChQuery": alertsInfo.AlertsWithLogsChQuery,
}
// send event only if there are dashboards or alerts or channels
if (dashboardsInfo.TotalDashboards > 0 || alertsInfo.TotalAlerts > 0 || len(*channels) > 0 || savedViewsInfo.TotalSavedViews > 0) && apiErr == nil {
for _, user := range users {
if user.Email == DEFAULT_CLOUD_EMAIL {
continue
}
telemetry.SendEvent(TELEMETRY_EVENT_DASHBOARDS_ALERTS, dashboardsAlertsData, user.Email, false, false)
} }
telemetry.SendEvent(TELEMETRY_EVENT_DASHBOARDS_ALERTS, dashboardsAlertsData, user.Email, false, false)
} }
} }
} }
@ -467,11 +448,9 @@ func getOutboundIP() string {
} }
defer resp.Body.Close() defer resp.Body.Close()
ipBody, err := io.ReadAll(resp.Body)
if err == nil { if err == nil {
ipBody, err := io.ReadAll(resp.Body) ip = ipBody
if err == nil {
ip = ipBody
}
} }
return string(ip) return string(ip)