diff --git a/pkg/apis/notify/config.go b/pkg/apis/notify/config.go index f8b7f66610..d49989a979 100644 --- a/pkg/apis/notify/config.go +++ b/pkg/apis/notify/config.go @@ -165,8 +165,9 @@ type SendParams struct { DomainId string RemoteTemplateParam SRemoteTemplateParam GroupKey string - GroupTimes uint - ReceiverId string + // minutes + GroupTimes uint + ReceiverId string } type SRemoteTemplateParam struct { diff --git a/pkg/apis/notify/subscriber.go b/pkg/apis/notify/subscriber.go index d55bc4aa33..531fe6437d 100644 --- a/pkg/apis/notify/subscriber.go +++ b/pkg/apis/notify/subscriber.go @@ -57,6 +57,8 @@ type SubscriberCreateInput struct { // description: scope // enum: system,domain Scope string + // minutes + GroupTimes *uint32 } type SubscriberChangeInput struct { @@ -72,6 +74,8 @@ type SubscriberChangeInput struct { // description: Robot(Id or Name) which is required when the type is 'robot' will Subscribe TopicID Robot string + // minutes + GroupTimes *uint32 } type SubscriberListInput struct { diff --git a/pkg/mcclient/options/notify/subscription.go b/pkg/mcclient/options/notify/subscription.go index 44d68d8301..e47fe6db6c 100644 --- a/pkg/mcclient/options/notify/subscription.go +++ b/pkg/mcclient/options/notify/subscription.go @@ -43,6 +43,8 @@ type SubscriberCreateOptions struct { RoleScope string `help:"required if type is 'role'"` Robot string `help:"required if type is 'robot'"` Scope string `positional:"true"` + // minutes + GroupTimes int } func (sc *SubscriberCreateOptions) Params() (jsonutils.JSONObject, error) { @@ -79,6 +81,8 @@ type SubscriberChangeOptions struct { Role string RoleScope string Robot string + // minutes + GroupTimes *int } func (ssr *SubscriberChangeOptions) Params() (jsonutils.JSONObject, error) { diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go index cd597385c7..ca0200189f 100644 --- a/pkg/notify/models/notification.go +++ b/pkg/notify/models/notification.go @@ -163,7 +163,7 @@ func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.T return err } for i := range input.Receivers { - _, err := ReceiverNotificationManager.Create(ctx, userCred, input.Receivers[i], n.Id) + _, err := ReceiverNotificationManager.Create(ctx, userCred, input.Receivers[i], 0, n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.Create") } @@ -175,7 +175,7 @@ func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.T } } for i := range input.Robots { - _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, input.Robots[i], n.Id) + _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, input.Robots[i], 0, n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot") } @@ -214,36 +214,45 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred if topic == nil { return output, nil } - var receiverIds []string + receiverIds := make(map[string]uint32) receiverIds1, err := SubscriberManager.getReceiversSent(ctx, topic.Id, input.ProjectDomainId, input.ProjectId) if err != nil { return output, errors.Wrap(err, "unable to get receive") } - receiverIds = append(receiverIds, receiverIds1...) + for k, v := range receiverIds1 { + receiverIds[k] = v + } // robot - var robots []string + robots := make(map[string]uint32) _robots, err := SubscriberManager.robot(topic.Id, input.ProjectDomainId, input.ProjectId) if err != nil { if errors.Cause(err) != errors.ErrNotFound { return output, errors.Wrapf(err, "unable fetch robot of subscription %q", topic.Id) } } else { - robots = append(robots, _robots...) + for robot, groupTime := range _robots { + robots[robot] = groupTime + } } var webhookRobots []string + realRobot := make(map[string]uint32) if len(robots) > 0 { - robots = sets.NewString(robots...).UnsortedList() - rs, err := RobotManager.FetchByIdOrNames(ctx, robots...) + robotList := []string{} + for robot := range robots { + robotList = append(robotList, robot) + } + rs, err := RobotManager.FetchByIdOrNames(ctx, robotList...) if err != nil { return output, errors.Wrap(err, "unable to get robots") } - robots, webhookRobots = make([]string, 0, len(rs)), make([]string, 0, 1) + + webhookRobots = make([]string, 0, 1) for i := range rs { if rs[i].Type == api.ROBOT_TYPE_WEBHOOK { webhookRobots = append(webhookRobots, rs[i].Id) } else { - robots = append(robots, rs[i].Id) + realRobot[rs[i].Id] = robots[rs[i].Id] } } } @@ -252,26 +261,32 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred // append default receiver if len(input.Event) == 0 { - receiverIds = append(receiverIds, input.ReceiverIds...) + for _, receiver := range input.ReceiverIds { + // receiverIds = append(receiverIds, api.SReceiverWithGroupTimes{ReceiverId: receiver}) + if _, ok := receiverIds[receiver]; !ok { + receiverIds[receiver] = 0 + } + } } - // fillter non-existed receiver - receivers, err := ReceiverManager.FetchByIdOrNames(ctx, receiverIds...) - if err != nil { - return output, errors.Wrap(err, "unable to fetch receivers by ids") + + receiverIdList := []string{} + for k, _ := range receiverIds { + receiverIdList = append(receiverIdList, k) } + receivers, err := ReceiverManager.FetchByIdOrNames(ctx, receiverIdList...) webconsoleContacts := sets.NewString() idSet := sets.NewString() for i := range receivers { idSet.Insert(receivers[i].Id) } - for _, re := range receiverIds { - if idSet.Has(re) { - continue - } + for re := range receiverIds { webconsoleContacts.Insert(re) } - receiverIds = idSet.UnsortedList() + realReceiverIds := make(map[string]uint32) + for _, id := range receiverIdList { + realReceiverIds[id] = receiverIds[id] + } // create event event, err := EventManager.CreateEvent(ctx, input.Event, topic.Id, message, string(input.Action), input.ResourceType, input.AdvanceDays) if err != nil { @@ -280,7 +295,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred if nm.needWebconsole([]STopic{*topic}) { // webconsole - err = nm.create(ctx, userCred, api.WEBCONSOLE, receiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.GetId(), topic.GetId(), topic.Type) + err = nm.create(ctx, userCred, api.WEBCONSOLE, realReceiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.GetId(), topic.GetId(), topic.Type) if err != nil { output.FailedList = append(output.FailedList, api.FailedElem{ ContactType: api.WEBCONSOLE, @@ -293,7 +308,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred if ct == api.MOBILE { continue } - err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, event.GetId(), topic.GetId(), topic.Type) + err := nm.create(ctx, userCred, ct, realReceiverIds, nil, input.Priority, event.GetId(), topic.GetId(), topic.Type) if err != nil { output.FailedList = append(output.FailedList, api.FailedElem{ ContactType: ct, @@ -309,7 +324,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred }) } // robot - err = nm.createWithRobots(ctx, userCred, robots, input.Priority, event.GetId(), topic.Type) + err = nm.createWithRobots(ctx, userCred, realRobot, input.Priority, event.GetId(), topic.Type) if err != nil { output.FailedList = append(output.FailedList, api.FailedElem{ ContactType: api.ROBOT, @@ -328,7 +343,7 @@ func (nm *SNotificationManager) needWebconsole(topics []STopic) bool { return false } -func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, eventId, topicId string, topicType string) error { +func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds map[string]uint32, contacts []string, priority, eventId, topicId string, topicType string) error { if len(receiverIds)+len(contacts) == 0 { return nil } @@ -346,8 +361,8 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To if err != nil { return errors.Wrap(err, "unable to insert Notification") } - for i := range receiverIds { - _, err := ReceiverNotificationManager.Create(ctx, userCred, receiverIds[i], n.Id) + for receiver := range receiverIds { + _, err := ReceiverNotificationManager.Create(ctx, userCred, receiver, receiverIds[receiver], n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.Create") } @@ -381,7 +396,7 @@ func (nm *SNotificationManager) createWithWebhookRobots(ctx context.Context, use } n.Id = db.DefaultUUIDGenerator() for i := range webhookRobotIds { - _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, webhookRobotIds[i], n.Id) + _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, webhookRobotIds[i], 0, n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot") } @@ -398,7 +413,7 @@ func (nm *SNotificationManager) createWithWebhookRobots(ctx context.Context, use return task.ScheduleRun(nil) } -func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds []string, priority, eventId string, topicType string) error { +func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds map[string]uint32, priority, eventId string, topicType string) error { if len(robotIds) == 0 { return nil } @@ -411,7 +426,7 @@ func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred m } n.Id = db.DefaultUUIDGenerator() for i := range robotIds { - _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, robotIds[i], n.Id) + _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, i, robotIds[i], n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot") } @@ -430,7 +445,7 @@ func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred m return nil } -func (n *SNotification) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverIds, contacts []string) error { +func (n *SNotification) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverIds map[string]uint32, contacts []string) error { if len(receiverIds)+len(contacts) == 0 { return nil } @@ -440,8 +455,8 @@ func (n *SNotification) Create(ctx context.Context, userCred mcclient.TokenCrede if err != nil { return errors.Wrap(err, "unable to insert Notification") } - for i := range receiverIds { - _, err := ReceiverNotificationManager.Create(ctx, userCred, receiverIds[i], n.Id) + for receiver := range receiverIds { + _, err := ReceiverNotificationManager.Create(ctx, userCred, receiver, receiverIds[receiver], n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.Create") } @@ -713,7 +728,6 @@ func (n *SNotification) GetTemplate(ctx context.Context, topicId, lang string, n groupKeys = *topic.GroupKeys } - out.GroupTimes = uint(topic.GroupTimes) rtStr, aStr, resultStr := event.ResourceType(), string(event.Action()), string(event.Result()) msgObj, err := jsonutils.ParseString(no.Message) if err != nil { @@ -741,7 +755,6 @@ func (n *SNotification) GetTemplate(ctx context.Context, topicId, lang string, n out.GroupKey += keyValue } } - out.GroupTimes = uint(topic.GroupTimes) if lang == "" { lang = getLangSuffix(ctx) } diff --git a/pkg/notify/models/receiver_notification.go b/pkg/notify/models/receiver_notification.go index d6bc414bd2..c7b099d327 100644 --- a/pkg/notify/models/receiver_notification.go +++ b/pkg/notify/models/receiver_notification.go @@ -67,6 +67,8 @@ type SReceiverNotification struct { SendBy string `width:"128"` Status string `width:"36" charset:"ascii"` FailedReason string `width:"1024"` + // minutes + GroupTimes uint32 } func (self *SReceiverNotificationManager) InitializeData() error { @@ -81,10 +83,11 @@ func (self *SReceiverNotification) GetReceiver() (*SReceiver, error) { return recv.(*SReceiver), nil } -func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverID, notificationID string) (*SReceiverNotification, error) { +func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverID string, groupTimes uint32, notificationID string) (*SReceiverNotification, error) { rn := &SReceiverNotification{ ReceiverID: receiverID, NotificationID: notificationID, + GroupTimes: groupTimes, ReceiverType: api.RECEIVER_TYPE_USER, Status: api.RECEIVER_NOTIFICATION_RECEIVED, SendBy: userCred.GetUserId(), @@ -92,13 +95,14 @@ func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mc return rn, rnm.TableSpec().Insert(ctx, rn) } -func (rnm *SReceiverNotificationManager) CreateRobot(ctx context.Context, userCred mcclient.TokenCredential, RobotID, notificationID string) (*SReceiverNotification, error) { +func (rnm *SReceiverNotificationManager) CreateRobot(ctx context.Context, userCred mcclient.TokenCredential, RobotID string, groupTimes uint32, notificationID string) (*SReceiverNotification, error) { rn := &SReceiverNotification{ ReceiverID: RobotID, NotificationID: notificationID, ReceiverType: api.RECEIVER_TYPE_ROBOT, Status: api.RECEIVER_NOTIFICATION_RECEIVED, SendBy: userCred.GetUserId(), + GroupTimes: groupTimes, } return rn, rnm.TableSpec().Insert(ctx, rn) } diff --git a/pkg/notify/models/subscriber.go b/pkg/notify/models/subscriber.go index c2bd7eec0b..58670d7123 100644 --- a/pkg/notify/models/subscriber.go +++ b/pkg/notify/models/subscriber.go @@ -89,6 +89,8 @@ type SSubscriber struct { ResourceAttributionName string `width:"128" charset:"utf8" list:"user" create:"optional"` Scope string `width:"128" charset:"ascii" nullable:"false" create:"required"` DomainId string `width:"128" charset:"ascii" nullable:"false" create:"optional"` + // minutes + GroupTimes uint32 `nullable:"true" list:"user" update:"user"` } func (sm *SSubscriberManager) validateReceivers(ctx context.Context, receivers []string) ([]string, error) { @@ -227,6 +229,11 @@ func (sm *SSubscriberManager) ValidateCreateData(ctx context.Context, userCred m return input, httperrors.NewForbiddenError("repeated with existing subscribers") } } + if input.GroupTimes != nil { + if *input.GroupTimes < 0 { + return input, httperrors.NewInputParameterError("invalidate group_times %d", input.GroupTimes) + } + } return input, nil } @@ -308,6 +315,16 @@ func (s *SSubscriber) PerformChange(ctx context.Context, userCred mcclient.Token return nil, errors.Wrap(err, "unable to update subscriber") } } + if input.GroupTimes != nil { + _, err := db.Update(s, func() error { + s.GroupTimes = *input.GroupTimes + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "unable to update subscriber group_times") + } + } + return nil, nil } @@ -433,14 +450,15 @@ func (s *SSubscriber) roleIdentification(ctx context.Context) (api.Identificatio return ret, nil } -func (srm *SSubscriberManager) robot(tid, projectDomainId, projectId string) ([]string, error) { +func (srm *SSubscriberManager) robot(tid, projectDomainId, projectId string) (map[string]uint32, error) { srs, err := srm.findSuitableOnes(tid, projectDomainId, projectId, api.SUBSCRIBER_TYPE_ROBOT) if err != nil { return nil, err } - robotIds := make([]string, len(srs)) + robotIds := make(map[string]uint32) for i := range srs { - robotIds[i] = srs[i].Identification + // robotIds[i] = srs[i].Identification + robotIds[srs[i].Identification] = srs[i].GroupTimes } return robotIds, nil } @@ -474,30 +492,34 @@ func (srm *SSubscriberManager) findSuitableOnes(tid, projectDomainId, projectId } // TODO: Use cache to increase speed -func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, projectDomainId string, projectId string) ([]string, error) { +func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, projectDomainId string, projectId string) (map[string]uint32, error) { srs, err := srm.findSuitableOnes(tid, projectDomainId, projectId, api.SUBSCRIBER_TYPE_RECEIVER, api.SUBSCRIBER_TYPE_ROLE) if err != nil { return nil, err } - receivers := make([]string, 0, len(srs)) + receivers := make(map[string]uint32) roleMap := make(map[string][]string, 3) - receivermap := make(map[string]*[]string, 3) + receivermap := make(map[string]map[string]uint32, 3) + identificationMapToSub := make(map[string]SSubscriber) for _, sr := range srs { if sr.Type == api.SUBSCRIBER_TYPE_RECEIVER { rIds, err := sr.getReceivers() if err != nil { return nil, errors.Wrap(err, "unable to get receivers") } - receivers = append(receivers, rIds...) + for _, receiveId := range rIds { + // receivers = append(receivers, api.SReceiverWithGroupTimes{ReceiverId: receiveId, GroupTimes: sr.GroupTimes}) + receivers[receiveId] = sr.GroupTimes + } } else if sr.Type == api.SUBSCRIBER_TYPE_ROLE { + identificationMapToSub[sr.Identification] = sr roleMap[sr.RoleScope] = append(roleMap[sr.RoleScope], sr.Identification) - receivermap[sr.RoleScope] = &[]string{} + receivermap[sr.RoleScope] = map[string]uint32{} } } errgo, _ := errgroup.WithContext(ctx) for _scope, _roles := range roleMap { scope, roles := _scope, _roles - receivers := receivermap[scope] errgo.Go(func() error { query := jsonutils.NewDict() query.Set("roles", jsonutils.NewStringArray(roles)) @@ -528,7 +550,9 @@ func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, if err != nil { return errors.Wrap(err, "unable to get user.id from result of RoleAssignments.List") } - *receivers = append(*receivers, id) + if _, ok := receivermap[scope][id]; !ok { + receivermap[scope][id] = identificationMapToSub[scope].GroupTimes + } } } return nil @@ -538,11 +562,16 @@ func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, if err != nil { return nil, err } + for _, res := range receivermap { - receivers = append(receivers, *res...) + for receive, time := range res { + if _, ok := receivers[receive]; !ok { + receivers[receive] = time + } + } } // de-duplication - return sets.NewString(receivers...).UnsortedList(), nil + return receivers, nil } func (sr *SSubscriber) getReceivers() ([]string, error) { diff --git a/pkg/notify/models/topic.go b/pkg/notify/models/topic.go index 7139d0f9b1..6ca84f8b05 100644 --- a/pkg/notify/models/topic.go +++ b/pkg/notify/models/topic.go @@ -82,7 +82,6 @@ type STopic struct { ContentCn string `length:"medium" nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"` ContentEn string `length:"medium" nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"` GroupKeys *api.STopicGroupKeys `nullable:"true" list:"user" update:"user"` - GroupTimes uint32 `nullable:"true" list:"user" update:"user"` AdvanceDays []int `nullable:"true" charset:"utf8" list:"user" update:"user" create:"optional"` WebconsoleDisable tristate.TriState @@ -316,7 +315,6 @@ func (sm *STopicManager) InitializeData() error { t.TitleEn = api.COMMON_TITLE_EN groupKeys := []string{"action_display"} t.GroupKeys = (*api.STopicGroupKeys)(&groupKeys) - t.GroupTimes = 60 case DefaultSystemExceptionEvent: t.addResources( notify.TOPIC_RESOURCE_HOST, @@ -391,7 +389,6 @@ func (sm *STopicManager) InitializeData() error { t.TitleEn = api.SYNC_ACCOUNT_STATUS_TITLE_EN groupKeys := []string{"name"} t.GroupKeys = (*api.STopicGroupKeys)(&groupKeys) - t.GroupTimes = 60 case DefaultNetOutOfSync: t.addResources( notify.TOPIC_RESOURCE_NET, @@ -407,7 +404,6 @@ func (sm *STopicManager) InitializeData() error { t.TitleEn = api.NET_OUT_OF_SYNC_TITLE_EN groupKeys := []string{"service_name"} t.GroupKeys = (*api.STopicGroupKeys)(&groupKeys) - t.GroupTimes = 60 case DefaultMysqlOutOfSync: t.addResources( notify.TOPIC_RESOURCE_DBINSTANCE, @@ -423,7 +419,6 @@ func (sm *STopicManager) InitializeData() error { t.TitleEn = api.MYSQL_OUT_OF_SYNC_TITLE_EN groupKeys := []string{"ip"} t.GroupKeys = (*api.STopicGroupKeys)(&groupKeys) - t.GroupTimes = 60 case DefaultServiceAbnormal: t.addResources( notify.TOPIC_RESOURCE_SERVICE, @@ -439,7 +434,6 @@ func (sm *STopicManager) InitializeData() error { t.TitleEn = api.SERVICE_ABNORMAL_TITLE_EN groupKeys := []string{"service_name"} t.GroupKeys = (*api.STopicGroupKeys)(&groupKeys) - t.GroupTimes = 60 case DefaultServerPanicked: t.addResources( notify.TOPIC_RESOURCE_SERVER, @@ -455,7 +449,6 @@ func (sm *STopicManager) InitializeData() error { t.TitleEn = api.SERVER_PANICKED_TITLE_EN groupKeys := []string{"name"} t.GroupKeys = (*api.STopicGroupKeys)(&groupKeys) - t.GroupTimes = 60 case DefaultPasswordExpire: t.addResources( notify.TOPIC_RESOURCE_USER, @@ -518,7 +511,6 @@ func (sm *STopicManager) InitializeData() error { topic.Results = t.Results topic.WebconsoleDisable = t.WebconsoleDisable topic.GroupKeys = t.GroupKeys - topic.GroupTimes = t.GroupTimes if len(topic.AdvanceDays) == 0 { topic.AdvanceDays = t.AdvanceDays } diff --git a/pkg/notify/tasks/notifications_send_task.go b/pkg/notify/tasks/notifications_send_task.go index 664adf2b70..dec7226e9f 100644 --- a/pkg/notify/tasks/notifications_send_task.go +++ b/pkg/notify/tasks/notifications_send_task.go @@ -264,6 +264,7 @@ func (notificationSendTask *NotificationSendTask) batchSend(ctx context.Context, params.Header = robot.Header params.Body = robot.Body params.MsgKey = robot.MsgKey + params.GroupTimes = uint(receivers[i].rNotificaion.GroupTimes) err = driver.Send(ctx, params) if err != nil { fails = append(fails, FailedReceiverSpec{ReceiverSpec: receivers[i], Reason: err.Error()}) @@ -271,6 +272,7 @@ func (notificationSendTask *NotificationSendTask) batchSend(ctx context.Context, } else if receivers[i].receiver.IsReceiver() { receiver := receivers[i].receiver.(*models.SReceiver) params.Receivers.Contact, _ = receiver.GetContact(notification.ContactType) + params.GroupTimes = uint(receivers[i].rNotificaion.GroupTimes) driver := models.GetDriver(notification.ContactType) if notification.ContactType == apis.EMAIL { params.EmailMsg = apis.SEmailMessage{