From 1d1bb6c381e47edbb7f7efc46a9a213842135dfa Mon Sep 17 00:00:00 2001 From: rainzm Date: Tue, 9 Mar 2021 19:29:27 +0800 Subject: [PATCH 1/7] feat(notify): advanced notify config and robot 1. Notify config has now become a domain resource, so that each domain can have its own configuration about notify. The domains has no configuration will use system config. 2. Robot separates from config and becomes a shared project resource. Sending information to robot can choose which robot to send to. --- cmd/climc/shell/notifyv2/config.go | 121 +--- cmd/climc/shell/notifyv2/notification.go | 3 + cmd/climc/shell/notifyv2/receiver.go | 1 + cmd/climc/shell/notifyv2/robot.go | 32 + pkg/apis/notify/config.go | 19 +- pkg/apis/notify/const.go | 14 + pkg/apis/notify/notification.go | 3 + pkg/apis/notify/robot.go | 43 ++ pkg/cloudcommon/notifyclient/notify.go | 19 +- .../notifyclient/notify_internal.go | 42 +- pkg/cloudcommon/notifyclient/template.go | 23 + pkg/mcclient/modules/mod_notify.go | 11 +- pkg/mcclient/modules/notify/consts.go | 1 + .../modules/notify/mod_notification.go | 3 + pkg/mcclient/options/notify/config.go | 82 +++ pkg/mcclient/options/notify/receiver.go | 10 +- pkg/mcclient/options/notify/robot.go | 69 ++ pkg/notify/interface.go | 19 +- pkg/notify/models/config.go | 234 +++++- pkg/notify/models/initdb.go | 1 + pkg/notify/models/notification.go | 53 +- pkg/notify/models/receiver.go | 36 +- pkg/notify/models/receiver_notification.go | 99 ++- pkg/notify/models/robot.go | 282 ++++++++ pkg/notify/rpc/apis/send_client.go | 28 +- pkg/notify/rpc/apis/send_server.pb.go | 673 +++++++++++++++--- pkg/notify/rpc/apis/send_server.proto | 53 +- pkg/notify/rpc/send.go | 266 +++++-- pkg/notify/service/handlers.go | 2 + pkg/notify/tasks/notifications_send_task.go | 210 ++++-- pkg/notify/tasks/repull_subcontact_task.go | 8 + pkg/notify/tasks/subcontact_pull_task.go | 2 +- pkg/notify/tasks/verification_send_task.go | 6 +- 33 files changed, 2030 insertions(+), 438 deletions(-) create mode 100644 cmd/climc/shell/notifyv2/robot.go create mode 100644 pkg/apis/notify/robot.go create mode 100644 pkg/mcclient/options/notify/config.go create mode 100644 pkg/mcclient/options/notify/robot.go create mode 100644 pkg/notify/models/robot.go diff --git a/cmd/climc/shell/notifyv2/config.go b/cmd/climc/shell/notifyv2/config.go index 0741883deb..46dec492ae 100644 --- a/cmd/climc/shell/notifyv2/config.go +++ b/cmd/climc/shell/notifyv2/config.go @@ -15,121 +15,16 @@ package notifyv2 import ( - "strings" - - "yunion.io/x/jsonutils" - - "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/options" + options "yunion.io/x/onecloud/pkg/mcclient/options/notify" ) func init() { - type ConfigListOptions struct { - options.BaseListOptions - } - R(&ConfigListOptions{}, "notify-config-list", "List notify config", func(s *mcclient.ClientSession, args *ConfigListOptions) error { - params, err := options.ListStructToParams(args) - if err != nil { - return err - } - result, err := modules.NotifyConfig.List(s, params) - if err != nil { - return err - } - printList(result, modules.NotifyConfig.GetColumns(s)) - return nil - }) - type ConfigCreateOptions struct { - TYPE string `help:"Type contact config"` - Configs []string `help:"Config content, format: 'key:value'"` - } - R(&ConfigCreateOptions{}, "notify-config-create", "Create notify config", func(s *mcclient.ClientSession, args *ConfigCreateOptions) error { - configs := jsonutils.NewDict() - for _, kv := range args.Configs { - index := strings.IndexByte(kv, ':') - configs.Set(kv[:index], jsonutils.NewString(kv[index+1:])) - } - params := jsonutils.NewDict() - params.Set("type", jsonutils.NewString(args.TYPE)) - params.Set("content", configs) - ret, err := modules.NotifyConfig.Create(s, params) - if err != nil { - return err - } - printObject(ret) - return nil - }) - R(&ConfigCreateOptions{}, "notify-config-update", "Update notify config", func(s *mcclient.ClientSession, args *ConfigCreateOptions) error { - configs := jsonutils.NewDict() - for _, kv := range args.Configs { - index := strings.IndexByte(kv, ':') - configs.Set(kv[:index], jsonutils.NewString(kv[index+1:])) - } - params := jsonutils.NewDict() - params.Set("content", configs) - - id, err := configIdFromType(s, args.TYPE) - if err != nil { - return err - } - ret, err := modules.NotifyConfig.Update(s, id, params) - if err != nil { - return err - } - printObject(ret) - return nil - }) - type ConfigOptions struct { - TYPE string `help:"Type contact config"` - } - R(&ConfigOptions{}, "notify-config-delete", "Delete notify config", func(s *mcclient.ClientSession, args *ConfigOptions) error { - id, err := configIdFromType(s, args.TYPE) - if err != nil { - return err - } - ret, err := modules.NotifyConfig.Delete(s, id, nil) - if err != nil { - return err - } - printObject(ret) - return nil - }) - R(&ConfigOptions{}, "notify-config-show", "Show notify config", func(s *mcclient.ClientSession, args *ConfigOptions) error { - listParams := jsonutils.NewDict() - listParams.Set("type", jsonutils.NewString(args.TYPE)) - list, err := modules.NotifyConfig.List(s, listParams) - if err != nil { - return err - } - data := list.Data[0] - printObject(data) - return nil - }) - type ConfigGetTypesOptions struct { - Robot string `json:"robot" choices:"yes|no|only"` - } - R(&ConfigGetTypesOptions{}, "notify-config-get-types", "Get all Config types", func(s *mcclient.ClientSession, args *ConfigGetTypesOptions) error { - param := jsonutils.Marshal(args) - result, err := modules.NotifyReceiver.PerformClassAction(s, "get-types", param) - if err != nil { - return err - } - printObject(result) - return nil - }) -} - -func configIdFromType(s *mcclient.ClientSession, t string) (string, error) { - listParams := jsonutils.NewDict() - listParams.Set("type", jsonutils.NewString(t)) - list, err := modules.NotifyConfig.List(s, listParams) - if err != nil { - return "", err - } - id, err := list.Data[0].GetString("id") - if err != nil { - return "", err - } - return id, nil + cmd := shell.NewResourceCmd(&modules.NotifyConfig).WithKeyword("notify-config") + cmd.List(new(options.ConfigListOptions)) + cmd.Create(new(options.ConfigCreateOptions)) + cmd.Update(new(options.ConfigUpdateOptions)) + cmd.Show(new(options.ConfigOptions)) + cmd.Delete(new(options.ConfigOptions)) } diff --git a/cmd/climc/shell/notifyv2/notification.go b/cmd/climc/shell/notifyv2/notification.go index 48b0868212..af8db36775 100644 --- a/cmd/climc/shell/notifyv2/notification.go +++ b/cmd/climc/shell/notifyv2/notification.go @@ -27,6 +27,7 @@ import ( func init() { type NotificationCreateInput struct { Receivers []string `help:"ID or Name of Receiver"` + Robots []string `help:"ID or Name of Robot"` ContactType string `help:"Contact type of receiver"` TOPIC string `help:"Topic"` Priority string `help:"Priority"` @@ -41,6 +42,7 @@ func init() { if args.Oldsdk { msg := notify.SNotifyMessage{ Uid: args.Receivers, + Robots: args.Robots, ContactType: notify.TNotifyChannel(args.ContactType), Topic: args.TOPIC, Priority: notify.TNotifyPriority(args.Priority), @@ -55,6 +57,7 @@ func init() { } else { input := api.NotificationCreateInput{ Receivers: args.Receivers, + Robots: args.Robots, ContactType: args.ContactType, Topic: args.TOPIC, Priority: args.Priority, diff --git a/cmd/climc/shell/notifyv2/receiver.go b/cmd/climc/shell/notifyv2/receiver.go index ea69b9f454..76bc71a99e 100644 --- a/cmd/climc/shell/notifyv2/receiver.go +++ b/cmd/climc/shell/notifyv2/receiver.go @@ -32,4 +32,5 @@ func init() { cmd.Perform("trigger-verify", new(options.ReceiverTriggerVerifyOptions)) cmd.Perform("verify", new(options.ReceiverVerifyOptions)) cmd.PerformClass("intellij-get", new(options.ReceiverIntellijGetOptions)) + cmd.PerformClass("get-types", new(options.ReceiverGetTypeOptions)) } diff --git a/cmd/climc/shell/notifyv2/robot.go b/cmd/climc/shell/notifyv2/robot.go new file mode 100644 index 0000000000..bbd7d8a63b --- /dev/null +++ b/cmd/climc/shell/notifyv2/robot.go @@ -0,0 +1,32 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package notifyv2 + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + options "yunion.io/x/onecloud/pkg/mcclient/options/notify" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.NotifyRobot).WithKeyword("notify-robot") + cmd.List(new(options.RobotListOptions)) + cmd.Create(new(options.RobotCreateOptions)) + cmd.Update(new(options.RobotUpdateOptions)) + cmd.Show(new(options.RobotOptions)) + cmd.Delete(new(options.RobotOptions)) + cmd.Perform("enable", new(options.RobotOptions)) + cmd.Perform("disable", new(options.RobotOptions)) +} diff --git a/pkg/apis/notify/config.go b/pkg/apis/notify/config.go index c56f7b261c..258c6abd2c 100644 --- a/pkg/apis/notify/config.go +++ b/pkg/apis/notify/config.go @@ -22,6 +22,7 @@ import ( type ConfigCreateInput struct { apis.StandaloneResourceCreateInput + apis.DomainizedResourceInput // description: config type // required: true @@ -32,6 +33,12 @@ type ConfigCreateInput struct { // required: true // example: {"app_id": "123456", "app_secret": "feishu_nihao"} Content jsonutils.JSONObject `json:"content"` + + // description: attribution + // required: true + // enum: system,domain + // example: system + Attribution string `json:"attribution"` } type ConfigUpdateInput struct { @@ -43,13 +50,16 @@ type ConfigUpdateInput struct { type ConfigDetails struct { apis.StandaloneResourceDetails + apis.DomainizedResourceInfo SConfig } type ConfigListInput struct { apis.StandaloneResourceListInput - Type string `json:"type"` + apis.DomainizedResourceListInput + Type string `json:"type"` + Attribution string `json:"attribution"` } type ConfigValidateInput struct { @@ -70,10 +80,9 @@ type ConfigValidateOutput struct { } type ConfigManagerGetTypesInput struct { - // description: Filter about robot - // enum: no,yes,only - // example: yes - Robot string `json:"robot"` + // description: domain where in config + // example: default + Domain string `json:"domain"` } type ConfigManagerGetTypesOutput struct { diff --git a/pkg/apis/notify/const.go b/pkg/apis/notify/const.go index 314306559c..89bdd3acc2 100644 --- a/pkg/apis/notify/const.go +++ b/pkg/apis/notify/const.go @@ -70,6 +70,20 @@ const ( CTYPE_ROBOT_YES = "yes" CTYPE_ROBOT_ONLY = "only" + CONFIG_ATTRIBUTION_SYSTEM = "system" + CONFIG_ATTRIBUTION_DOMAIN = "domain" + + ROBOT_TYPE_FEISHU = "feishu" + ROBOT_TYPE_DINGTALK = "dingtalk" + ROBOT_TYPE_WORKWX = "workwx" + ROBOT_TYPE_WEBHOOK = "webhook" + + ROBOT_STATUS_READY = "ready" + + RECEIVER_TYPE_USER = "user" + RECEIVER_TYPE_CONTACT = "contact" + RECEIVER_TYPE_ROBOT = "robot" + SUBSCRIPTION_RESOURCE_CREATE_DELETE = "resource create delete" SUBSCRIPTION_RESOURCE_CHANGECONFIG = "resource change config" SUBSCRIPTION_RESOURCE_UPDATE = "resource udpate" diff --git a/pkg/apis/notify/notification.go b/pkg/apis/notify/notification.go index 26b7ad778d..0abe056c1e 100644 --- a/pkg/apis/notify/notification.go +++ b/pkg/apis/notify/notification.go @@ -32,6 +32,9 @@ type NotificationCreateInput struct { // description: direct contact, admin privileges required // required: false Contacts []string `json:"contacts"` + // description: robots + // example: feishu robot + Robots []string `json:"robots"` // description: contact type // required: ture // example: email diff --git a/pkg/apis/notify/robot.go b/pkg/apis/notify/robot.go new file mode 100644 index 0000000000..2c989065f0 --- /dev/null +++ b/pkg/apis/notify/robot.go @@ -0,0 +1,43 @@ +package notify + +import "yunion.io/x/onecloud/pkg/apis" + +type RobotCreateInput struct { + apis.SharableVirtualResourceCreateInput + // description: robot type + // enum: feishu,dingtalk,workwx,webhook + // example: webhook + Type string `json:"type"` + // description: address + // example: http://helloworld.io/test/webhook + Address string `json:"address"` + // description: Language preference + // example: zh_CN + Lang string `json:"lang"` +} + +type RobotDetails struct { + apis.SharableVirtualResourceDetails +} + +type RobotListInput struct { + apis.SharableVirtualResourceListInput + apis.EnabledResourceBaseListInput + // description: robot type + // enum: feishu,dingtalk,workwx,webhook + // example: webhook + Type string `json:"type"` + // description: Language preference + // example: en + Lang string `json:"lang"` +} + +type RobotUpdateInput struct { + apis.SharableVirtualResourceBaseUpdateInput + // description: address + // example: http://helloworld.io/test/webhook + Address string `json:"address"` + // description: Language preference + // example: en + Lang string `json:"lang"` +} diff --git a/pkg/cloudcommon/notifyclient/notify.go b/pkg/cloudcommon/notifyclient/notify.go index c2c5b8271e..2b975975a1 100644 --- a/pkg/cloudcommon/notifyclient/notify.go +++ b/pkg/cloudcommon/notifyclient/notify.go @@ -119,22 +119,29 @@ func NotifyCriticalWithCtx(ctx context.Context, recipientId []string, isGroup bo // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot. func NotifyAllWithoutRobot(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { - return notifyRobot(context.Background(), "no", recipientId, isGroup, priority, event, data) + return notifyAll(context.Background(), recipientId, isGroup, priority, event, data) } // NotifyAllWithoutRobot will send messages via all contacnt type from exclude robot contact type such as dingtalk-robot. func NotifyAllWithoutRobotWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { - return notifyRobot(ctx, "no", recipientId, isGroup, priority, event, data) + return notifyAll(ctx, recipientId, isGroup, priority, event, data) } // NotifyRobot will send messages via all robot contact type such as dingtalk-robot. -func NotifyRobot(recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { - return notifyRobot(context.Background(), "only", recipientId, isGroup, priority, event, data) +func NotifyRobot(robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { + return NotifyRobotWithCtx(context.Background(), robotIds, priority, event, data) } // NotifyRobot will send messages via all robot contact type such as dingtalk-robot. -func NotifyRobotWithCtx(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { - return notifyRobot(ctx, "only", recipientId, isGroup, priority, event, data) +func NotifyRobotWithCtx(ctx context.Context, robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { + rawNotify(context.Background(), sNotifyParams{ + robots: robotIds, + channel: npk.NotifyByRobot, + priority: priority, + event: event, + data: data, + }) + return nil } func SystemNotify(priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { diff --git a/pkg/cloudcommon/notifyclient/notify_internal.go b/pkg/cloudcommon/notifyclient/notify_internal.go index 9c93aee904..b83bea5b90 100644 --- a/pkg/cloudcommon/notifyclient/notify_internal.go +++ b/pkg/cloudcommon/notifyclient/notify_internal.go @@ -57,13 +57,12 @@ func systemNotify(ctx context.Context, priority npk.TNotifyPriority, event strin notify(ctx, notifyAdminGroups, true, priority, event, data) } -func notifyRobot(ctx context.Context, robot string, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { +func notifyAll(ctx context.Context, recipientId []string, isGroup bool, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { s, err := AdminSessionGenerator(ctx, consts.GetRegion(), "") if err != nil { return err } params := jsonutils.NewDict() - params.Set("robot", jsonutils.NewString(robot)) result, err := modules.NotifyReceiver.PerformClassAction(s, "get-types", params) if err != nil { return err @@ -85,6 +84,30 @@ func notifyRobot(ctx context.Context, robot string, recipientId []string, isGrou type sTarget struct { reIds []string contacts []string + robots []string +} + +func langRobot(ctx context.Context, robots []string) (map[language.Tag]*sTarget, error) { + contextLang := i18n.Lang(ctx) + robotLang, err := getRobotLang(robots) + if err != nil { + return nil, err + } + ret := make(map[language.Tag]*sTarget) + for robot, langStr := range robotLang { + lang, err := language.Parse(langStr) + if err != nil { + log.Errorf("can't parse %s to language.Tag: %v", langStr, err) + lang = contextLang + } + t := ret[lang] + if t == nil { + ret[lang] = &sTarget{} + t = ret[lang] + } + t.robots = append(t.robots, robot) + } + return ret, nil } func lang(ctx context.Context, contactType npk.TNotifyChannel, reIds []string, contacts []string) (map[language.Tag]*sTarget, error) { @@ -175,6 +198,7 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, msg := npk.SNotifyMessage{} msg.Uid = reIds msg.Priority = p.priority + msg.Robots = p.robots msg.Contacts = p.contacts msg.ContactType = p.channel msg.Topic = p.event @@ -185,9 +209,15 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, return []npk.SNotifyMessage{msg}, nil } - langMap, err := lang(ctx, p.channel, reIds, p.contacts) - if err != nil { - return nil, err + var langMap map[language.Tag]*sTarget + + if p.channel == npk.NotifyByRobot { + langMap, err = langRobot(ctx, p.robots) + } else { + langMap, err = lang(ctx, p.channel, reIds, p.contacts) + if err != nil { + return nil, err + } } msgs := make([]npk.SNotifyMessage, 0, len(langMap)) @@ -196,6 +226,7 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, msg := npk.SNotifyMessage{} msg.Uid = t.reIds msg.Priority = p.priority + msg.Robots = p.robots msg.Contacts = t.contacts msg.ContactType = p.channel topic, _ := getContent(langSuffix, p.event, "title", p.channel, p.data) @@ -218,6 +249,7 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, type sNotifyParams struct { recipientId []string + robots []string isGroup bool contacts []string channel npk.TNotifyChannel diff --git a/pkg/cloudcommon/notifyclient/template.go b/pkg/cloudcommon/notifyclient/template.go index 3148e1aec1..2cd3e2e549 100644 --- a/pkg/cloudcommon/notifyclient/template.go +++ b/pkg/cloudcommon/notifyclient/template.go @@ -64,6 +64,29 @@ func getUserLang(uids []string) (map[string]string, error) { return uidLang, nil } +func getRobotLang(robots []string) (map[string]string, error) { + s, err := AdminSessionGenerator(context.Background(), consts.GetRegion(), "") + if err != nil { + return nil, err + } + robotLang := make(map[string]string) + if len(robots) > 0 { + params := jsonutils.NewDict() + params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(robots, ",")))) + params.Set("scope", jsonutils.NewString("system")) + ret, err := modules.NotifyRobot.List(s, params) + if err != nil { + return nil, err + } + for i := range ret.Data { + id, _ := ret.Data[i].GetString("id") + langStr, _ := ret.Data[i].GetString("lang") + robotLang[id] = langStr + } + } + return robotLang, nil +} + func init() { templatesTableLock = &sync.Mutex{} templatesTable = make(map[string]*template.Template) diff --git a/pkg/mcclient/modules/mod_notify.go b/pkg/mcclient/modules/mod_notify.go index 67b363e7d8..75ccc1fd1b 100644 --- a/pkg/mcclient/modules/mod_notify.go +++ b/pkg/mcclient/modules/mod_notify.go @@ -23,6 +23,7 @@ type ConfigsManager struct { var ( NotifyReceiver modulebase.ResourceManager NotifyConfig modulebase.ResourceManager + NotifyRobot modulebase.ResourceManager Notification modulebase.ResourceManager NotifyTemplate modulebase.ResourceManager NotifySubscription modulebase.ResourceManager @@ -41,11 +42,19 @@ func init() { NotifyConfig = NewNotifyv2Manager( "notifyconfig", "notifyconfigs", - []string{"Type", "Content"}, + []string{"Name", "Type", "Content", "Attribution", "Project_Domain"}, []string{}, ) register(&NotifyConfig) + NotifyRobot = NewNotifyv2Manager( + "robot", + "robots", + []string{"ID", "Name", "Type", "Address", "Lang"}, + []string{}, + ) + register(&NotifyRobot) + Notification = NewNotifyv2Manager( "notification", "notifications", diff --git a/pkg/mcclient/modules/notify/consts.go b/pkg/mcclient/modules/notify/consts.go index afa81e5803..b65d695814 100644 --- a/pkg/mcclient/modules/notify/consts.go +++ b/pkg/mcclient/modules/notify/consts.go @@ -29,6 +29,7 @@ const ( NotifyByWebConsole = TNotifyChannel("webconsole") NotifyByFeishu = TNotifyChannel("feishu") NotifyByWorkwx = TNotifyChannel("workwx") + NotifyByRobot = TNotifyChannel("robot") NotifyFeishuRobot = TNotifyChannel("feishu-robot") NotifyByDingTalkRobot = TNotifyChannel("dingtalk-robot") diff --git a/pkg/mcclient/modules/notify/mod_notification.go b/pkg/mcclient/modules/notify/mod_notification.go index 4773a6c9cd..6fe8fbe6d0 100644 --- a/pkg/mcclient/modules/notify/mod_notification.go +++ b/pkg/mcclient/modules/notify/mod_notification.go @@ -31,6 +31,7 @@ var ( type SNotifyMessage struct { Uid []string `json:"uid,omitempty"` Gid []string `json:"gid,omitempty"` + Robots []string `json:"robots,omitempty"` ContactType TNotifyChannel `json:"contact_type,omitempty"` Contacts []string `json:"contracts"` Topic string `json:"topic,omitempty"` @@ -46,6 +47,7 @@ type SNotifyMessage struct { type SNotifyV2Message struct { Receivers []string `json:"receivers"` Contacts []string `json:"contacts"` + Robots []string `json:"robots"` ContactType string `json:"contact_type"` Topic string `json:"topic"` Priority string `json:"priority"` @@ -83,6 +85,7 @@ func (manager *NotificationManager) Send(s *mcclient.ClientSession, msg SNotifyM v2msg := SNotifyV2Message{ Receivers: receiverIds, Contacts: msg.Contacts, + Robots: msg.Robots, ContactType: string(msg.ContactType), Topic: msg.Topic, Priority: string(msg.Priority), diff --git a/pkg/mcclient/options/notify/config.go b/pkg/mcclient/options/notify/config.go new file mode 100644 index 0000000000..c9363c2559 --- /dev/null +++ b/pkg/mcclient/options/notify/config.go @@ -0,0 +1,82 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package notify + +import ( + "strings" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type ConfigListOptions struct { + options.BaseListOptions + Type string `json:"type"` + Attribution string `json:"attribution"` +} + +func (cl *ConfigListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(cl) +} + +type ConfigCreateOptions struct { + NAME string `help:"Name of Config"` + Domain string `help:"which domain create for, required if attribution is 'domain'"` + Type string `help:"The type of config"` + Configs []string `help:"Config content, format: 'key:value'"` + Attribution string `help:"Attribution" choices:"system|domain"` +} + +func (cc *ConfigCreateOptions) Params() (jsonutils.JSONObject, error) { + jo := jsonutils.Marshal(cc) + d := jo.(*jsonutils.JSONDict) + d.Remove("configs") + configs := jsonutils.NewDict() + for _, kv := range cc.Configs { + index := strings.IndexByte(kv, ':') + configs.Set(kv[:index], jsonutils.NewString(kv[index+1:])) + } + d.Set("content", configs) + return d, nil +} + +type ConfigOptions struct { + ID string +} + +func (c *ConfigOptions) GetId() string { + return c.ID +} + +func (c *ConfigOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type ConfigUpdateOptions struct { + ConfigOptions + Configs []string +} + +func (cu *ConfigUpdateOptions) Params() (jsonutils.JSONObject, error) { + configs := jsonutils.NewDict() + for _, kv := range cu.Configs { + index := strings.IndexByte(kv, ':') + configs.Set(kv[:index], jsonutils.NewString(kv[index+1:])) + } + d := jsonutils.NewDict() + d.Set("content", configs) + return d, nil +} diff --git a/pkg/mcclient/options/notify/receiver.go b/pkg/mcclient/options/notify/receiver.go index 099f6e60bf..38f2e77cd2 100644 --- a/pkg/mcclient/options/notify/receiver.go +++ b/pkg/mcclient/options/notify/receiver.go @@ -31,7 +31,7 @@ func (rc *ReceiverCreateOptions) Params() (jsonutils.JSONObject, error) { d := jsonutils.NewDict() d.Set("uid", jsonutils.NewString(rc.UID)) d.Set("email", jsonutils.NewString(rc.Email)) - d.Set("enabled_contact_type", jsonutils.NewStringArray(rc.EnabledContactTypes)) + d.Set("enabled_contact_types", jsonutils.NewStringArray(rc.EnabledContactTypes)) d.Add(jsonutils.NewString(rc.Mobile), "international_mobile", "mobile") d.Add(jsonutils.NewString(rc.MobileAreaCode), "international_mobile", "area_code") return d, nil @@ -110,3 +110,11 @@ type ReceiverIntellijGetOptions struct { func (ri *ReceiverIntellijGetOptions) Params() (jsonutils.JSONObject, error) { return jsonutils.Marshal(ri), nil } + +type ReceiverGetTypeOptions struct { + Domain string `help:"Domain under where available contact methods"` +} + +func (rg *ReceiverGetTypeOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(rg), nil +} diff --git a/pkg/mcclient/options/notify/robot.go b/pkg/mcclient/options/notify/robot.go new file mode 100644 index 0000000000..dac8ade0da --- /dev/null +++ b/pkg/mcclient/options/notify/robot.go @@ -0,0 +1,69 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package notify + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type RobotListOptions struct { + options.BaseListOptions + Lang string + Type string `choices:"feishu|dingtalk|workwx|webhook"` + Enabled bool +} + +func (rl *RobotListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(rl) +} + +type RobotCreateOptions struct { + NAME string + Type string `choices:"feishu|dingtalk|workwx|webhook"` + Address string + Lang string +} + +func (rc *RobotCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(rc), nil +} + +type RobotOptions struct { + ID string `help:"Id or Name of robot"` +} + +func (r *RobotOptions) GetId() string { + return r.ID +} + +func (r *RobotOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type RobotUpdateOptions struct { + RobotOptions + SrobotUpdateOptions +} + +type SrobotUpdateOptions struct { + Address string + Lang string +} + +func (ru *RobotUpdateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(ru.SrobotUpdateOptions), nil +} diff --git a/pkg/notify/interface.go b/pkg/notify/interface.go index 014c80ed1b..fcc4a9d837 100644 --- a/pkg/notify/interface.go +++ b/pkg/notify/interface.go @@ -27,10 +27,13 @@ type INotifyService interface { InitAll() error StopAll() UpdateServices(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) - RestartService(ctx context.Context, config SConfig, serviceName string) + UpdateConfig(ctx context.Context, service string, config SConfig) error Send(ctx context.Context, contactType string, args apis.SendParams) error - ContactByMobile(ctx context.Context, mobile, serviceName string) (string, error) + ContactByMobile(ctx context.Context, mobile, serviceName, domainId string) (string, error) BatchSend(ctx context.Context, contactType string, args apis.BatchSendParams) ([]*apis.FailedRecord, error) + SendRobotMessage(ctx context.Context, rType string, receivers []*apis.SReceiver, title string, message string) ([]*apis.FailedRecord, error) + AddConfig(ctx context.Context, service string, config SConfig) error + DeleteConfig(ctx context.Context, service, domainId string) error ValidateConfig(ctx context.Context, cType string, configs map[string]string) (isValid bool, message string, err error) } @@ -53,8 +56,11 @@ type SBatchSendParams struct { } type IServiceConfigStore interface { - GetConfig(serviceName string) (SConfig, error) - SetConfig(serviceName string, config SConfig) error + GetConfigs(service string) ([]SConfig, error) + GetConfig(service, domainId string) (SConfig, error) + SetConfig(service string, config SConfig) error + HasSystemConfig(service string) (bool, error) + BatchCheckConfig(service string, domainIds []string) ([]bool, error) } type SNotification struct { @@ -70,7 +76,10 @@ type ITemplateStore interface { FillWithTemplate(ctx context.Context, lang string, notification SNotification) (params apis.SendParams, err error) } -type SConfig map[string]string +type SConfig struct { + Config map[string]string + DomainId string +} var ( ErrNoSuchMobile = errors.Error("no such mobile") diff --git a/pkg/notify/models/config.go b/pkg/notify/models/config.go index 4306cab612..1c57783544 100644 --- a/pkg/notify/models/config.go +++ b/pkg/notify/models/config.go @@ -26,8 +26,10 @@ import ( "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" @@ -40,6 +42,7 @@ import ( type SConfigManager struct { db.SStandaloneResourceBaseManager + db.SDomainizedResourceBaseManager } var ConfigManager *SConfigManager @@ -58,9 +61,11 @@ func init() { type SConfig struct { db.SStandaloneResourceBase + db.SDomainizedResourceBase - Type string `width:"15" nullable:"false" create:"required" get:"admin" list:"admin"` - Content jsonutils.JSONObject `nullable:"false" create:"required" update:"admin" get:"admin" list:"admin"` + Type string `width:"15" nullable:"false" create:"required" get:"domain" list:"domain"` + Content jsonutils.JSONObject `nullable:"false" create:"required" update:"domain" get:"domain" list:"domain"` + Attribution string `width:"8" nullable:"false" default:"system" get:"domain" list:"domain"` } func (cm *SConfigManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ConfigCreateInput) (api.ConfigCreateInput, error) { @@ -69,13 +74,29 @@ func (cm *SConfigManager) ValidateCreateData(ctx context.Context, userCred mccli if err != nil { return input, err } - if !utils.IsInStringArray(input.Type, []string{api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WEBCONSOLE, api.WORKWX, api.FEISHU_ROBOT, api.DINGTALK_ROBOT, api.WORKWX_ROBOT, api.WEBHOOK}) { + log.Infof("input: %s", jsonutils.Marshal(input)) + if len(input.ProjectDomainId) > 0 { + _, input.DomainizedResourceInput, err = db.ValidateDomainizedResourceInput(ctx, input.DomainizedResourceInput) + if err != nil { + return input, err + } + } + if !utils.IsInStringArray(input.Type, []string{api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WEBCONSOLE, api.WORKWX}) { return input, httperrors.NewInputParameterError("unkown type %q", input.Type) } + if !utils.IsInStringArray(input.Attribution, []string{api.CONFIG_ATTRIBUTION_SYSTEM, api.CONFIG_ATTRIBUTION_DOMAIN}) { + return input, httperrors.NewInputParameterError("invalid attribution, need %q or %q", api.CONFIG_ATTRIBUTION_SYSTEM, api.CONFIG_ATTRIBUTION_DOMAIN) + } + if input.Attribution == api.CONFIG_ATTRIBUTION_SYSTEM { + allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), ConfigManager.KeywordPlural(), policy.PolicyActionCreate) + if allowScope != rbacutils.ScopeSystem { + return input, httperrors.NewInputParameterError("No permission to set %q attribution", api.CONFIG_ATTRIBUTION_SYSTEM) + } + } if input.Content == nil { return input, httperrors.NewMissingParameterError("content") } - config, err := cm.GetConfigByType(input.Type) + config, err := cm.Config(input.Type, input.ProjectDomainId) if err == nil && config != nil { return input, httperrors.NewDuplicateResourceError("duplicate type %q", input.Type) } @@ -104,6 +125,21 @@ func (cm *SConfigManager) ValidateCreateData(ctx context.Context, userCred mccli return input, nil } +func (c *SConfig) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + err := c.SStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) + if err != nil { + return err + } + if c.Attribution == api.CONFIG_ATTRIBUTION_DOMAIN || c.Attribution == "" { + c.Attribution = api.CONFIG_ATTRIBUTION_DOMAIN + c.DomainId, _ = data.GetString("project_domain_id") + if c.DomainId == "" { + c.DomainId = userCred.GetProjectDomainId() + } + } + return nil +} + func (c *SConfig) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigUpdateInput) (api.ConfigUpdateInput, error) { // validate configs := make(map[string]string) @@ -129,7 +165,18 @@ func (c *SConfig) ValidateUpdateData(ctx context.Context, userCred mcclient.Toke } func (c *SConfig) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { - err := c.StartRepullSubcontactTask(ctx, userCred) + c.SStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + configMap := make(map[string]string) + err := c.Content.Unmarshal(&configMap) + if err != nil { + log.Errorf("unable to unmarshal: %v", err) + return + } + NotifyService.AddConfig(ctx, c.Type, notifyv2.SConfig{ + Config: configMap, + DomainId: c.DomainId, + }) + err = c.StartRepullSubcontactTask(ctx, userCred) if err != nil { log.Errorf("unable to StartRepullSubcontactTask: %v", err) } @@ -143,13 +190,27 @@ func (c *SConfig) PostUpdate(ctx context.Context, userCred mcclient.TokenCredent log.Errorf("unable to unmarshal: %v", err) return } - NotifyService.RestartService(ctx, configMap, c.Type) + NotifyService.UpdateConfig(ctx, c.Type, notifyv2.SConfig{ + Config: configMap, + DomainId: c.DomainId, + }) err = c.StartRepullSubcontactTask(ctx, userCred) if err != nil { log.Errorf("unable to StartRepullSubcontactTask: %v", err) } } +func (c *SConfig) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { + c.SStandaloneResourceBase.PreDelete(ctx, userCred) + NotifyService.DeleteConfig(ctx, c.Type, c.DomainId) +} +func (c *SConfig) PostDelete(ctx context.Context, userCred mcclient.TokenCredential) { + err := c.StartRepullSubcontactTask(ctx, userCred) + if err != nil { + log.Errorf("unable to StartRepullSubcontactTask: %v", err) + } +} + func (c *SConfig) StartRepullSubcontactTask(ctx context.Context, userCred mcclient.TokenCredential) error { task, err := taskman.TaskManager.NewTask(ctx, "RepullSuncontactTask", c, userCred, nil, "", "") if err != nil { @@ -159,19 +220,8 @@ func (c *SConfig) StartRepullSubcontactTask(ctx context.Context, userCred mcclie return nil } -func (cm *SConfigManager) filterContactType(cTypes []string, robot string) []string { - switch robot { - case api.CTYPE_ROBOT_ONLY: - return intersection(cTypes, RobotContactTypes) - case api.CTYPE_ROBOT_YES: - return cTypes - default: - return difference(cTypes, RobotContactTypes) - } -} - var sortedCTypes = []string{ - api.WEBCONSOLE, api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WORKWX, api.DINGTALK_ROBOT, api.FEISHU_ROBOT, api.WORKWX_ROBOT, + api.WEBCONSOLE, api.EMAIL, api.MOBILE, api.DINGTALK, api.FEISHU, api.WORKWX, } func sortContactType(ctypes []string) []string { @@ -185,6 +235,25 @@ func sortContactType(ctypes []string) []string { return ret } +func (cm *SConfigManager) availableContactTypes(domainId string) ([]string, error) { + q := cm.Query("type") + q = q.Filter(sqlchemy.OR(sqlchemy.AND(sqlchemy.Equals(q.Field("attribution"), api.CONFIG_ATTRIBUTION_DOMAIN), sqlchemy.Equals(q.Field("domain_id"), domainId)), sqlchemy.Equals(q.Field("attribution"), api.CONFIG_ATTRIBUTION_SYSTEM))) + + allTypes := make([]struct { + Type string + }, 0, 3) + err := q.All(&allTypes) + if err != nil { + return nil, err + } + ret := make([]string, len(allTypes)) + for i := range ret { + ret[i] = allTypes[i].Type + } + // De-duplication + return sets.NewString(ret...).UnsortedList(), nil +} + func (cm *SConfigManager) allContactType() ([]string, error) { q := cm.Query("type") allTypes := make([]struct { @@ -206,10 +275,18 @@ func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQue if err != nil { return nil, err } + q, err = self.SDomainizedResourceBaseManager.ListItemFilter(ctx, q, userCred, input.DomainizedResourceListInput) + if err != nil { + return nil, err + } q = q.NotEquals("type", api.WEBCONSOLE) if len(input.Type) > 0 { q.Filter(sqlchemy.Equals(q.Field("type"), input.Type)) } + if len(input.Attribution) > 0 { + q = q.Equals("attribution", input.Attribution) + } + return q, nil } @@ -226,9 +303,11 @@ func (cm *SConfigManager) FetchCustomizeColumns( isList bool, ) []api.ConfigDetails { sRows := cm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + dRows := cm.SDomainizedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) rows := make([]api.ConfigDetails, len(objs)) for i := range rows { rows[i].StandaloneResourceDetails = sRows[i] + rows[i].DomainizedResourceInfo = dRows[i] } return rows } @@ -238,6 +317,10 @@ func (cm *SConfigManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field stri if err != nil { return q, nil } + q, err = cm.SDomainizedResourceBaseManager.QueryDistinctExtraField(q, field) + if err != nil { + return q, nil + } return q, nil } @@ -246,6 +329,10 @@ func (cm *SConfigManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQ if err != nil { return nil, err } + q, err = cm.SDomainizedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.DomainizedResourceListInput) + if err != nil { + return nil, err + } return q, nil } @@ -378,7 +465,18 @@ func (self *SConfigManager) InitializeData() error { } func (cm *SConfigManager) ResourceScope() rbacutils.TRbacScope { - return rbacutils.ScopeSystem + return rbacutils.ScopeDomain +} + +func (cm *SConfigManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + switch scope { + case rbacutils.ScopeDomain, rbacutils.ScopeProject: + q = q.Equals("attribution", api.CONFIG_ATTRIBUTION_DOMAIN) + if owner != nil { + q = q.Equals("domain_id", owner.GetProjectDomainId()) + } + } + return q } func (cm *SConfigManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -398,36 +496,114 @@ func (c *SConfig) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCr } // Fetch all SConfig struct which type is contactType. -func (self *SConfigManager) GetConfigByType(contactType string) (*SConfig, error) { - var config SConfig +func (self *SConfigManager) Configs(contactType string) ([]SConfig, error) { + var configs = make([]SConfig, 0, 2) q := self.Query() q.Filter(sqlchemy.Equals(q.Field("type"), contactType)) + err := q.All(&configs) + if err != nil { + return nil, errors.Wrapf(err, "fail to fetch SConfigs by type %s", contactType) + } + return configs, nil +} + +func (self *SConfigManager) Config(contactType, domainId string) (*SConfig, error) { + q := self.Query() + q = q.Equals("type", contactType) + if len(domainId) == 0 { + q = q.Equals("attribution", api.CONFIG_ATTRIBUTION_SYSTEM) + } else { + q = q.Equals("domain_id", domainId).Equals("attribution", api.CONFIG_ATTRIBUTION_DOMAIN) + } + var config SConfig err := q.First(&config) if err != nil { - return nil, errors.Wrap(err, "fail to fetch SConfigs by type") + return nil, errors.Wrapf(err, "fail to fetch SConfig by type %s and domain %s", contactType, domainId) } return &config, nil } -func (self *SConfigManager) GetConfig(contactType string) (notifyv2.SConfig, error) { - config, err := self.GetConfigByType(contactType) +func (self *SConfigManager) HasSystemConfig(contactType string) (bool, error) { + q := self.Query().Equals("type", contactType).Equals("attribution", "system") + c, err := q.CountWithError() if err != nil { - return nil, err + return false, err } - ret := make(map[string]string) - err = config.Content.Unmarshal(&ret) + return c > 0, nil +} + +func (self *SConfigManager) BatchCheckConfig(contactType string, domainIds []string) ([]bool, error) { + domainIdSet := sets.NewString(domainIds...) + var configs = make([]SConfig, 0, 2) + q := self.Query().Equals("type", contactType).Equals("attribution", "domain").In("domain_id", domainIdSet.UnsortedList()) + err := q.All(&configs) if err != nil { - return nil, errors.Wrap(err, "fail unmarshal config content") + return nil, errors.Wrapf(err, "fail to fetch SConfigs by type %s", contactType) + } + for i := range configs { + if domainIdSet.Has(configs[i].DomainId) { + domainIdSet.Delete(configs[i].DomainId) + } + } + ret := make([]bool, len(domainIds)) + for i := range domainIds { + if domainIdSet.Has(domainIds[i]) { + // no config of domainId, use default one + ret[i] = false + } + ret[i] = true } return ret, nil } +func (self *SConfigManager) GetConfigs(contactType string) ([]notifyv2.SConfig, error) { + configs, err := self.Configs(contactType) + if err != nil { + return nil, err + } + ret := make([]notifyv2.SConfig, 0, len(configs)) + for i := range configs { + c := make(map[string]string) + err := configs[i].Content.Unmarshal(&c) + if err != nil { + return nil, errors.Wrap(err, "fail unmarshal config content") + } + ret = append(ret, notifyv2.SConfig{ + Config: c, + DomainId: configs[i].DomainId, + }) + } + return ret, nil +} + +func (self *SConfigManager) GetConfig(contactType, domainId string) (notifyv2.SConfig, error) { + config, err := self.Config(contactType, domainId) + if err != nil { + return notifyv2.SConfig{}, err + } + ret := make(map[string]string) + err = config.Content.Unmarshal(&ret) + if err != nil { + return notifyv2.SConfig{}, errors.Wrap(err, "fail unmarshal config content") + } + return notifyv2.SConfig{ + Config: ret, + DomainId: config.DomainId, + }, nil +} + func (self *SConfigManager) SetConfig(contactType string, config notifyv2.SConfig) error { - content := jsonutils.Marshal(config) + content := jsonutils.Marshal(config.Config) sConfig := &SConfig{ Type: contactType, Content: content, } + sConfig.DomainId = config.DomainId + if sConfig.DomainId == "" { + sConfig.Attribution = api.CONFIG_ATTRIBUTION_SYSTEM + } else { + sConfig.Attribution = api.CONFIG_ATTRIBUTION_DOMAIN + } return self.TableSpec().InsertOrUpdate(context.Background(), sConfig) } diff --git a/pkg/notify/models/initdb.go b/pkg/notify/models/initdb.go index 2114489497..a5116a6f85 100644 --- a/pkg/notify/models/initdb.go +++ b/pkg/notify/models/initdb.go @@ -33,6 +33,7 @@ func InitDB() error { TemplateManager, ReceiverNotificationManager, SubscriptionManager, + RobotManager, } { err := manager.InitializeData() if err != nil { diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go index 8e349b5040..a1ebc1d091 100644 --- a/pkg/notify/models/notification.go +++ b/pkg/notify/models/notification.go @@ -80,25 +80,44 @@ const ( ) func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) { - if len(input.Tag) == 0 && utils.IsInStringArray(input.Tag, []string{api.NOTIFICATION_TAG_ALERT}) { + if len(input.Tag) > 0 && !utils.IsInStringArray(input.Tag, []string{api.NOTIFICATION_TAG_ALERT}) { return input, httperrors.NewInputParameterError("invalid tag") } - if len(input.Receivers) == 0 { + if len(input.Contacts) > 0 { if !userCred.IsAllow(rbacutils.ScopeSystem, api.SERVICE_TYPE, nm.KeywordPlural(), policy.PolicyActionPerform, SendByContact) { - return input, httperrors.NewForbiddenError("can't send notification by contact, need receiver") + return input, httperrors.NewForbiddenError("only admin can send notification by contact") } if len(input.Contacts) == 0 { input.Contacts = []string{""} } } + log.Infof("notify input: %s", jsonutils.Marshal(input)) - // check contact type enabled - allContactType, err := ConfigManager.allContactType() - if err != nil { - return input, err - } - if !utils.IsInStringArray(input.ContactType, allContactType) { - return input, httperrors.NewInputParameterError("Unconfigured contact type %q", input.ContactType) + // check robot + if len(input.Robots) > 0 { + input.ContactType = api.ROBOT + robots, err := RobotManager.FetchByIdOrNames(ctx, input.Robots...) + if err != nil { + return input, errors.Wrap(err, "RobotManager.FetchByIdOrNames") + } + idSet := sets.NewString() + nameSet := sets.NewString() + for i := range robots { + idSet.Insert(robots[i].Id) + nameSet.Insert(robots[i].Name) + } + for _, re := range input.Receivers { + if idSet.Has(re) || nameSet.Has(re) { + continue + } + if !input.IgnoreNonexistentReceiver { + return input, httperrors.NewInputParameterError("no such robot whose id is %q", re) + } + } + input.Robots = idSet.UnsortedList() + if len(input.Robots) == 0 { + return input, httperrors.NewInputParameterError("no valid receiver or contact") + } } // check receivers if len(input.Receivers) > 0 { @@ -138,10 +157,12 @@ func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred length = len(input.Topic) } name := fmt.Sprintf("%s-%s-%s", input.Topic[:length], input.ContactType, nowStr) + var err error input.Name, err = db.GenerateName(ctx, nm, ownerId, name) if err != nil { return input, errors.Wrapf(err, "unable to generate name for %s", name) } + log.Infof("after validatecreate input: %s", jsonutils.Marshal(input)) return input, nil } @@ -160,9 +181,15 @@ func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.T } } for i := range input.Contacts { - _, err := ReceiverNotificationManager.CreateWithoutReceiver(ctx, userCred, input.Contacts[i], n.Id) + _, err := ReceiverNotificationManager.CreateContact(ctx, userCred, input.Contacts[i], n.Id) if err != nil { - return errors.Wrap(err, "ReceiverNotificationManager.Create") + return errors.Wrap(err, "ReceiverNotificationManager.CreateContact") + } + } + for i := range input.Robots { + _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, input.Robots[i], n.Id) + if err != nil { + return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot") } } return nil @@ -345,7 +372,7 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To } } for i := range contacts { - _, err := ReceiverNotificationManager.CreateWithoutReceiver(ctx, userCred, contacts[i], n.Id) + _, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id) if err != nil { return errors.Wrap(err, "ReceiverNotificationManager.Create") } diff --git a/pkg/notify/models/receiver.go b/pkg/notify/models/receiver.go index 79fe4b2c71..48dd796b5d 100644 --- a/pkg/notify/models/receiver.go +++ b/pkg/notify/models/receiver.go @@ -284,6 +284,10 @@ func (rm *SReceiverManager) ValidateCreateData(ctx context.Context, userCred mcc return input, nil } +func (r *SReceiver) IsEnabled() bool { + return r.Enabled.Bool() +} + var LaxMobileRegexp = regexp.MustCompile(`[0-9]{6,14}`) func (r *SReceiver) IsEnabledContactType(ct string) (bool, error) { @@ -689,13 +693,18 @@ func (r *SReceiverManager) AllowPerformGetTypes(ctx context.Context, userCred mc return true } -func (cm *SReceiverManager) PerformGetTypes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigManagerGetTypesInput) (api.ConfigManagerGetTypesOutput, error) { +func (rm *SReceiverManager) PerformGetTypes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ConfigManagerGetTypesInput) (api.ConfigManagerGetTypesOutput, error) { output := api.ConfigManagerGetTypesOutput{} - allContactType, err := ConfigManager.allContactType() + t, err := db.TenantCacheManager.FetchDomainByIdOrName(ctx, input.Domain) if err != nil { - return output, err + return output, errors.Wrap(err, "unable to FetchDomainByIdOrName") } - output.Types = sortContactType(ConfigManager.filterContactType(allContactType, input.Robot)) + ret, err := ConfigManager.availableContactTypes(t.Id) + if err != nil { + return output, errors.Wrap(err, "unable to get available contact types") + } + + output.Types = sortContactType(ret) return output, nil } @@ -1183,12 +1192,10 @@ func (rm *SReceiverManager) FetchByIDs(ctx context.Context, ids ...string) ([]SR return contacts, nil } -func (rm *SReceiverManager) FetchByIdOrNames(ctx context.Context, idOrNames ...string) ([]SReceiver, error) { +func idOrNameFilter(q *sqlchemy.SQuery, idOrNames ...string) *sqlchemy.SQuery { if len(idOrNames) == 0 { - return nil, nil + return q } - var err error - q := rm.Query() var conds []sqlchemy.ICondition for _, idOrName := range idOrNames { conds = append(conds, sqlchemy.Equals(q.Field("name"), idOrName)) @@ -1201,6 +1208,15 @@ func (rm *SReceiverManager) FetchByIdOrNames(ctx context.Context, idOrNames ...s } else if len(conds) > 1 { q = q.Filter(sqlchemy.OR(conds...)) } + return q +} + +func (rm *SReceiverManager) FetchByIdOrNames(ctx context.Context, idOrNames ...string) ([]SReceiver, error) { + if len(idOrNames) == 0 { + return nil, nil + } + var err error + q := idOrNameFilter(rm.Query(), idOrNames...) receivers := make([]SReceiver, 0, len(idOrNames)) err = db.FetchModelObjects(rm, q, &receivers) if err != nil { @@ -1255,3 +1271,7 @@ func (r *SReceiver) GetContact(cType string) (string, error) { } return "", nil } + +func (r *SReceiver) GetDomainId() string { + return r.DomainId +} diff --git a/pkg/notify/models/receiver_notification.go b/pkg/notify/models/receiver_notification.go index ae2d525205..d04be9685b 100644 --- a/pkg/notify/models/receiver_notification.go +++ b/pkg/notify/models/receiver_notification.go @@ -43,6 +43,7 @@ type SReceiverNotification struct { NotificationID string `width:"128" charset:"ascii" nullable:"false" index:"true"` // ignore if ReceiverID is not empty or default Contact string `width:"128" nullable:"false" index:"true"` + ReceiverType string `width:"16"` SendAt time.Time `nullable:"false"` SendBy string `width:"128" nullable:"false"` Status string `width:"36" charset:"ascii"` @@ -57,6 +58,18 @@ func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mc rn := &SReceiverNotification{ ReceiverID: receiverID, NotificationID: notificationID, + ReceiverType: api.RECEIVER_TYPE_USER, + Status: api.RECEIVER_NOTIFICATION_RECEIVED, + SendBy: userCred.GetUserId(), + } + return rn, rnm.TableSpec().Insert(ctx, rn) +} + +func (rnm *SReceiverNotificationManager) CreateRobot(ctx context.Context, userCred mcclient.TokenCredential, RobotID, notificationID string) (*SReceiverNotification, error) { + rn := &SReceiverNotification{ + ReceiverID: RobotID, + NotificationID: notificationID, + ReceiverType: api.RECEIVER_TYPE_ROBOT, Status: api.RECEIVER_NOTIFICATION_RECEIVED, SendBy: userCred.GetUserId(), } @@ -71,10 +84,10 @@ func (rnm *SReceiverNotificationManager) GetSlaveFieldName() string { return "receiver_id" } -func (rnm *SReceiverNotificationManager) CreateWithoutReceiver(ctx context.Context, userCred mcclient.TokenCredential, contact, notificationID string) (*SReceiverNotification, error) { +func (rnm *SReceiverNotificationManager) CreateContact(ctx context.Context, userCred mcclient.TokenCredential, contact, notificationID string) (*SReceiverNotification, error) { rn := &SReceiverNotification{ NotificationID: notificationID, - ReceiverID: ReceiverIdDefault, + ReceiverType: api.RECEIVER_TYPE_CONTACT, Contact: contact, Status: api.RECEIVER_NOTIFICATION_RECEIVED, SendBy: userCred.GetUserId(), @@ -82,7 +95,18 @@ func (rnm *SReceiverNotificationManager) CreateWithoutReceiver(ctx context.Conte return rn, rnm.TableSpec().Insert(ctx, rn) } -func (rn *SReceiverNotification) Receiver() (*SReceiver, error) { +// func (rn *SReceiverNotification) Receiver() (*SReceiver, error) { +// q := ReceiverManager.Query().Equals("id", rn.ReceiverID) +// var receiver SReceiver +// err := q.First(&receiver) +// if err != nil { +// return nil, err +// } +// receiver.SetModelManager(ReceiverManager, &receiver) +// return &receiver, nil +// } + +func (rn *SReceiverNotification) receiver() (*SReceiver, error) { q := ReceiverManager.Query().Equals("id", rn.ReceiverID) var receiver SReceiver err := q.First(&receiver) @@ -93,6 +117,34 @@ func (rn *SReceiverNotification) Receiver() (*SReceiver, error) { return &receiver, nil } +func (rn *SReceiverNotification) robot() (*SRobot, error) { + q := RobotManager.Query().Equals("id", rn.ReceiverID) + var robot SRobot + err := q.First(&robot) + if err != nil { + return nil, err + } + robot.SetModelManager(RobotManager, &robot) + return &robot, nil +} + +func (rn *SReceiverNotification) Receiver() (IReceiver, error) { + switch rn.ReceiverType { + case api.RECEIVER_TYPE_USER: + return rn.receiver() + case api.RECEIVER_TYPE_CONTACT: + return &SContact{contact: rn.Contact}, nil + case api.RECEIVER_TYPE_ROBOT: + return rn.robot() + default: + // compatible + if rn.ReceiverID != "" && rn.ReceiverID != ReceiverIdDefault { + return rn.receiver() + } + return &SContact{contact: rn.Contact}, nil + } +} + func (rn *SReceiverNotification) BeforeSend(ctx context.Context, sendTime time.Time) error { if sendTime.IsZero() { sendTime = time.Now() @@ -117,3 +169,44 @@ func (rn *SReceiverNotification) AfterSend(ctx context.Context, success bool, re }) return err } + +type IReceiver interface { + IsEnabled() bool + GetDomainId() string + IsEnabledContactType(string) (bool, error) + IsVerifiedContactType(string) (bool, error) + GetContact(string) (string, error) + GetTemplateLang(context.Context) (string, error) +} + +type SReceiverBase struct { +} + +func (s SReceiverBase) IsEnabled() bool { + return true +} + +func (s SReceiverBase) GetDomainId() string { + return "" +} + +func (s SReceiverBase) IsEnabledContactType(_ string) (bool, error) { + return true, nil +} + +func (s SReceiverBase) IsVerifiedContactType(_ string) (bool, error) { + return true, nil +} + +func (s SReceiverBase) GetTemplateLang(ctx context.Context) (string, error) { + return "", nil +} + +type SContact struct { + SReceiverBase + contact string +} + +func (s *SContact) GetContact(_ string) (string, error) { + return s.contact, nil +} diff --git a/pkg/notify/models/robot.go b/pkg/notify/models/robot.go new file mode 100644 index 0000000000..9049593c23 --- /dev/null +++ b/pkg/notify/models/robot.go @@ -0,0 +1,282 @@ +package models + +import ( + "context" + "strings" + + "github.com/pkg/errors" + "golang.org/x/text/language" + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis" + idenapi "yunion.io/x/onecloud/pkg/apis/identity" + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + rpcapi "yunion.io/x/onecloud/pkg/notify/rpc/apis" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SRobotManager struct { + db.SSharableVirtualResourceBaseManager + db.SEnabledResourceBaseManager +} + +var RobotManager *SRobotManager + +func init() { + RobotManager = &SRobotManager{ + SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager( + SRobot{}, + "robots_tbl", + "robot", + "robots", + ), + } + RobotManager.SetVirtualObject(RobotManager) +} + +type SRobot struct { + db.SSharableVirtualResourceBase + db.SEnabledResourceBase + + Type string `width:"16" nullable:"false" create:"required" get:"user" list:"user" index:"true"` + Address string `nullable:"false" create:"required" update:"user" get:"user" list:"user"` + Lang string `width:"16" nullable:"false" create:"required" update:"user" get:"user" list:"user"` +} + +func (rm *SRobotManager) InitializeData() error { + log.Infof("start to init data for notify robot") + // convert robot config + q := ConfigManager.Query().In("type", append(RobotContactTypes, api.WEBHOOK)) + var configs []SConfig + err := db.FetchModelObjects(ConfigManager, q, &configs) + if err != nil { + return err + } + if len(configs) == 0 { + return nil + } + robots := make([]SRobot, len(configs)) + for i := range configs { + webhook, _ := configs[i].Content.GetString("webhook") + robot := SRobot{ + Address: webhook, + Lang: "zh_CN", + } + robot.IsPublic = true + robot.PublicScope = string(rbacutils.ScopeSystem) + robot.DomainId = idenapi.DEFAULT_DOMAIN_ID + robot.Status = api.RECEIVER_STATUS_READY + switch configs[i].Type { + case api.FEISHU_ROBOT: + robot.Type = api.ROBOT_TYPE_FEISHU + robot.Name = "Feishu Robot" + case api.DINGTALK_ROBOT: + robot.Type = api.ROBOT_TYPE_DINGTALK + robot.Name = "Dingtalk Robot" + case api.WORKWX_ROBOT: + robot.Type = api.ROBOT_TYPE_WORKWX + robot.Name = "Workwx Robot" + case api.WEBHOOK: + robot.Type = api.ROBOT_TYPE_WEBHOOK + robot.Name = "Webhook" + addresses := strings.Split(robot.Address, ",") + for i := 1; i < len(addresses); i++ { + robotn := robot + robotn.Address = strings.TrimSpace(addresses[i]) + robots = append(robots, robotn) + } + robot.Address = addresses[0] + } + robots = append(robots, robot) + } + ctx := context.Background() + // insert new robot + for i := range robots { + err := rm.TableSpec().Insert(ctx, &robots[i]) + if err != nil { + return err + } + } + // delete old configs + for i := range configs { + config := &configs[i] + _, err := db.Update(config, func() error { + return config.MarkDelete() + }) + if err != nil { + return err + } + } + return nil +} + +func (rm *SRobotManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.RobotCreateInput) (api.RobotCreateInput, error) { + var err error + input.SharableVirtualResourceCreateInput, err = rm.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ValidateCreateData") + } + // check type + if !utils.IsInStringArray(input.Type, []string{api.ROBOT_TYPE_FEISHU, api.ROBOT_TYPE_WORKWX, api.ROBOT_TYPE_DINGTALK, api.ROBOT_TYPE_WEBHOOK}) { + return input, httperrors.NewInputParameterError("unkown type %q", input.Type) + } + // check lang + _, err = language.Parse(input.Lang) + if err != nil { + return input, httperrors.NewInputParameterError("invalid lang %q: %s", input.Lang, err.Error()) + } + // check Address + records, err := NotifyService.SendRobotMessage(ctx, input.Type, []*rpcapi.SReceiver{ + { + Contact: input.Address, + DomainId: input.ProjectDomainId, + }, + }, "Validate", "This is a verification message, please ignore.") + if err != nil { + return input, errors.Wrap(err, "unable to validate address") + } + if len(records) > 0 { + return input, httperrors.NewInputParameterError("invalid address: %s", records[0].Reason) + } + return input, nil +} + +func (r *SRobot) Receiver() *rpcapi.SReceiver { + return &rpcapi.SReceiver{ + Contact: r.Address, + DomainId: r.DomainId, + } +} + +func (rm *SRobotManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.RobotDetails { + sRows := rm.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + rows := make([]api.RobotDetails, len(objs)) + for i := range rows { + rows[i].SharableVirtualResourceDetails = sRows[i] + } + return rows +} + +func (rm *SRobotManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.RobotListInput) (*sqlchemy.SQuery, error) { + q, err := rm.SSharableBaseResourceManager.ListItemFilter(ctx, q, userCred, input.SharableResourceBaseListInput) + if err != nil { + return nil, err + } + q, err = rm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput) + if err != nil { + return nil, err + } + if len(input.Type) > 0 { + q = q.Equals("type", input.Type) + } + if len(input.Lang) > 0 { + q = q.Equals("lang", input.Lang) + } + return q, nil +} + +func (r *SRobot) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RobotUpdateInput) (api.RobotUpdateInput, error) { + var err error + input.SharableVirtualResourceBaseUpdateInput, err = r.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.SharableVirtualResourceBaseUpdateInput) + if err != nil { + return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData") + } + // check lang + _, err = language.Parse(input.Lang) + if err != nil { + return input, httperrors.NewInputParameterError("invalid lang %q: %s", input.Lang, err.Error()) + } + // check Address + records, err := NotifyService.SendRobotMessage(ctx, r.Type, []*rpcapi.SReceiver{ + { + Contact: input.Address, + DomainId: r.DomainId, + }, + }, "Validate", "This is a verification message, please ignore.") + if err != nil { + return input, errors.Wrap(err, "unable to validate address") + } + if len(records) > 0 { + return input, httperrors.NewInputParameterError("invalid address: %s", records[0].Reason) + } + return input, nil +} + +func (r *SRobot) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + r.Status = api.ROBOT_STATUS_READY + return nil +} + +func (rm *SRobotManager) FetchByIdOrNames(ctx context.Context, idOrNames ...string) ([]SRobot, error) { + if len(idOrNames) == 0 { + return nil, nil + } + var err error + q := idOrNameFilter(rm.Query(), idOrNames...) + robots := make([]SRobot, 0, len(idOrNames)) + err = db.FetchModelObjects(rm, q, &robots) + if err != nil { + return nil, err + } + return robots, nil +} + +func (r *SRobot) IsEnabled() bool { + return r.Enabled.Bool() +} + +func (r *SRobot) IsEnabledContactType(ctype string) (bool, error) { + return ctype == api.ROBOT, nil +} + +func (r *SRobot) IsVerifiedContactType(ctype string) (bool, error) { + return ctype == api.ROBOT, nil +} + +func (r *SRobot) GetContact(ctype string) (string, error) { + return r.Address, nil +} + +func (r *SRobot) GetTemplateLang(ctx context.Context) (string, error) { + lang, err := language.Parse(r.Lang) + if err != nil { + return "", errors.Wrapf(err, "unable to prase language %q", r.Lang) + } + tLang := notifyclientI18nTable.LookupByLang(lang, tempalteLang) + return tLang, nil +} + +func (r *SRobot) GetDomainId() string { + return r.DomainId +} + +func (r *SRobot) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "enable") +} + +func (r *SRobot) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) { + err := db.EnabledPerformEnable(r, ctx, userCred, true) + if err != nil { + return nil, errors.Wrap(err, "EnabledPerformEnable") + } + return nil, nil +} + +func (r *SRobot) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "disable") +} + +func (r *SRobot) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformDisableInput) (jsonutils.JSONObject, error) { + err := db.EnabledPerformEnable(r, ctx, userCred, false) + if err != nil { + return nil, errors.Wrap(err, "EnabledPerformEnable") + } + return nil, nil +} diff --git a/pkg/notify/rpc/apis/send_client.go b/pkg/notify/rpc/apis/send_client.go index 64ad790004..3b9a913af8 100644 --- a/pkg/notify/rpc/apis/send_client.go +++ b/pkg/notify/rpc/apis/send_client.go @@ -41,13 +41,31 @@ func (c *SendNotificationClient) Send(ctx context.Context, in *SendParams, opts return c.sendAgentClient.Send(ctx, in, opts...) } -func (c *SendNotificationClient) UpdateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*Empty, error) { +func (c *SendNotificationClient) AddConfig(ctx context.Context, in *AddConfigInput, opts ...grpc.CallOption) (*Empty, error) { + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.AddConfig(ctx, in, opts...) +} + +func (c *SendNotificationClient) DeleteConfig(ctx context.Context, in *DeleteConfigInput, opts ...grpc.CallOption) (*Empty, error) { + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.DeleteConfig(ctx, in, opts...) +} + +func (c *SendNotificationClient) UpdateConfig(ctx context.Context, in *UpdateConfigInput, opts ...grpc.CallOption) (*Empty, error) { ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) defer cancel() return c.sendAgentClient.UpdateConfig(ctx, in, opts...) } -func (c *SendNotificationClient) ValidateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*ValidateConfigReply, error) { +func (c *SendNotificationClient) CompleteConfig(ctx context.Context, in *CompleteConfigInput, opts ...grpc.CallOption) (*Empty, error) { + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.CompleteConfig(ctx, in, opts...) +} + +func (c *SendNotificationClient) ValidateConfig(ctx context.Context, in *ValidateConfigInput, opts ...grpc.CallOption) (*ValidateConfigReply, error) { ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) defer cancel() return c.sendAgentClient.ValidateConfig(ctx, in, opts...) @@ -59,6 +77,12 @@ func (c *SendNotificationClient) UseridByMobile(ctx context.Context, in *UseridB return c.sendAgentClient.UseridByMobile(ctx, in, opts...) } +func (c *SendNotificationClient) Ready(ctx context.Context, in *ReadyInput, opts ...grpc.CallOption) (*ReadyOutput, error) { + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.Ready(ctx, in, opts...) +} + func (c *SendNotificationClient) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) { ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) defer cancel() diff --git a/pkg/notify/rpc/apis/send_server.pb.go b/pkg/notify/rpc/apis/send_server.pb.go index c3106b46b3..cb7e2c8626 100644 --- a/pkg/notify/rpc/apis/send_server.pb.go +++ b/pkg/notify/rpc/apis/send_server.pb.go @@ -6,12 +6,11 @@ package apis import ( context "context" fmt "fmt" - math "math" - proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" + math "math" ) // Reference imports to suppress errors if they are not otherwise used. @@ -26,15 +25,15 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type SendParams struct { - Contact string `protobuf:"bytes,1,opt,name=Contact,proto3" json:"Contact,omitempty"` - Topic string `protobuf:"bytes,2,opt,name=Topic,proto3" json:"Topic,omitempty"` - Title string `protobuf:"bytes,3,opt,name=Title,proto3" json:"Title,omitempty"` - Message string `protobuf:"bytes,4,opt,name=Message,proto3" json:"Message,omitempty"` - Priority string `protobuf:"bytes,5,opt,name=Priority,proto3" json:"Priority,omitempty"` - RemoteTemplate string `protobuf:"bytes,6,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Receiver *SReceiver `protobuf:"bytes,1,opt,name=Receiver,proto3" json:"Receiver,omitempty"` + Topic string `protobuf:"bytes,2,opt,name=Topic,proto3" json:"Topic,omitempty"` + Title string `protobuf:"bytes,3,opt,name=Title,proto3" json:"Title,omitempty"` + Message string `protobuf:"bytes,4,opt,name=Message,proto3" json:"Message,omitempty"` + Priority string `protobuf:"bytes,5,opt,name=Priority,proto3" json:"Priority,omitempty"` + RemoteTemplate string `protobuf:"bytes,6,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *SendParams) Reset() { *m = SendParams{} } @@ -62,11 +61,11 @@ func (m *SendParams) XXX_DiscardUnknown() { var xxx_messageInfo_SendParams proto.InternalMessageInfo -func (m *SendParams) GetContact() string { +func (m *SendParams) GetReceiver() *SReceiver { if m != nil { - return m.Contact + return m.Receiver } - return "" + return nil } func (m *SendParams) GetTopic() string { @@ -104,47 +103,220 @@ func (m *SendParams) GetRemoteTemplate() string { return "" } -type UpdateConfigParams struct { +type ValidateConfigInput struct { Configs map[string]string `protobuf:"bytes,1,rep,name=configs,proto3" json:"configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *UpdateConfigParams) Reset() { *m = UpdateConfigParams{} } -func (m *UpdateConfigParams) String() string { return proto.CompactTextString(m) } -func (*UpdateConfigParams) ProtoMessage() {} -func (*UpdateConfigParams) Descriptor() ([]byte, []int) { +func (m *ValidateConfigInput) Reset() { *m = ValidateConfigInput{} } +func (m *ValidateConfigInput) String() string { return proto.CompactTextString(m) } +func (*ValidateConfigInput) ProtoMessage() {} +func (*ValidateConfigInput) Descriptor() ([]byte, []int) { return fileDescriptor_63fdd68f7eb311f9, []int{1} } -func (m *UpdateConfigParams) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_UpdateConfigParams.Unmarshal(m, b) +func (m *ValidateConfigInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ValidateConfigInput.Unmarshal(m, b) } -func (m *UpdateConfigParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_UpdateConfigParams.Marshal(b, m, deterministic) +func (m *ValidateConfigInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ValidateConfigInput.Marshal(b, m, deterministic) } -func (m *UpdateConfigParams) XXX_Merge(src proto.Message) { - xxx_messageInfo_UpdateConfigParams.Merge(m, src) +func (m *ValidateConfigInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_ValidateConfigInput.Merge(m, src) } -func (m *UpdateConfigParams) XXX_Size() int { - return xxx_messageInfo_UpdateConfigParams.Size(m) +func (m *ValidateConfigInput) XXX_Size() int { + return xxx_messageInfo_ValidateConfigInput.Size(m) } -func (m *UpdateConfigParams) XXX_DiscardUnknown() { - xxx_messageInfo_UpdateConfigParams.DiscardUnknown(m) +func (m *ValidateConfigInput) XXX_DiscardUnknown() { + xxx_messageInfo_ValidateConfigInput.DiscardUnknown(m) } -var xxx_messageInfo_UpdateConfigParams proto.InternalMessageInfo +var xxx_messageInfo_ValidateConfigInput proto.InternalMessageInfo -func (m *UpdateConfigParams) GetConfigs() map[string]string { +func (m *ValidateConfigInput) GetConfigs() map[string]string { if m != nil { return m.Configs } return nil } +type AddConfigInput struct { + Configs map[string]string `protobuf:"bytes,1,rep,name=configs,proto3" json:"configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + DomainId string `protobuf:"bytes,2,opt,name=domainId,proto3" json:"domainId,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddConfigInput) Reset() { *m = AddConfigInput{} } +func (m *AddConfigInput) String() string { return proto.CompactTextString(m) } +func (*AddConfigInput) ProtoMessage() {} +func (*AddConfigInput) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{2} +} + +func (m *AddConfigInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddConfigInput.Unmarshal(m, b) +} +func (m *AddConfigInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddConfigInput.Marshal(b, m, deterministic) +} +func (m *AddConfigInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddConfigInput.Merge(m, src) +} +func (m *AddConfigInput) XXX_Size() int { + return xxx_messageInfo_AddConfigInput.Size(m) +} +func (m *AddConfigInput) XXX_DiscardUnknown() { + xxx_messageInfo_AddConfigInput.DiscardUnknown(m) +} + +var xxx_messageInfo_AddConfigInput proto.InternalMessageInfo + +func (m *AddConfigInput) GetConfigs() map[string]string { + if m != nil { + return m.Configs + } + return nil +} + +func (m *AddConfigInput) GetDomainId() string { + if m != nil { + return m.DomainId + } + return "" +} + +type UpdateConfigInput struct { + Configs map[string]string `protobuf:"bytes,1,rep,name=configs,proto3" json:"configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + DomainId string `protobuf:"bytes,2,opt,name=domainId,proto3" json:"domainId,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateConfigInput) Reset() { *m = UpdateConfigInput{} } +func (m *UpdateConfigInput) String() string { return proto.CompactTextString(m) } +func (*UpdateConfigInput) ProtoMessage() {} +func (*UpdateConfigInput) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{3} +} + +func (m *UpdateConfigInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateConfigInput.Unmarshal(m, b) +} +func (m *UpdateConfigInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateConfigInput.Marshal(b, m, deterministic) +} +func (m *UpdateConfigInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateConfigInput.Merge(m, src) +} +func (m *UpdateConfigInput) XXX_Size() int { + return xxx_messageInfo_UpdateConfigInput.Size(m) +} +func (m *UpdateConfigInput) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateConfigInput.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateConfigInput proto.InternalMessageInfo + +func (m *UpdateConfigInput) GetConfigs() map[string]string { + if m != nil { + return m.Configs + } + return nil +} + +func (m *UpdateConfigInput) GetDomainId() string { + if m != nil { + return m.DomainId + } + return "" +} + +type DeleteConfigInput struct { + DomainId string `protobuf:"bytes,1,opt,name=domainId,proto3" json:"domainId,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeleteConfigInput) Reset() { *m = DeleteConfigInput{} } +func (m *DeleteConfigInput) String() string { return proto.CompactTextString(m) } +func (*DeleteConfigInput) ProtoMessage() {} +func (*DeleteConfigInput) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{4} +} + +func (m *DeleteConfigInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DeleteConfigInput.Unmarshal(m, b) +} +func (m *DeleteConfigInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DeleteConfigInput.Marshal(b, m, deterministic) +} +func (m *DeleteConfigInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeleteConfigInput.Merge(m, src) +} +func (m *DeleteConfigInput) XXX_Size() int { + return xxx_messageInfo_DeleteConfigInput.Size(m) +} +func (m *DeleteConfigInput) XXX_DiscardUnknown() { + xxx_messageInfo_DeleteConfigInput.DiscardUnknown(m) +} + +var xxx_messageInfo_DeleteConfigInput proto.InternalMessageInfo + +func (m *DeleteConfigInput) GetDomainId() string { + if m != nil { + return m.DomainId + } + return "" +} + +type CompleteConfigInput struct { + ConfigInput []*AddConfigInput `protobuf:"bytes,1,rep,name=ConfigInput,proto3" json:"ConfigInput,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CompleteConfigInput) Reset() { *m = CompleteConfigInput{} } +func (m *CompleteConfigInput) String() string { return proto.CompactTextString(m) } +func (*CompleteConfigInput) ProtoMessage() {} +func (*CompleteConfigInput) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{5} +} + +func (m *CompleteConfigInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CompleteConfigInput.Unmarshal(m, b) +} +func (m *CompleteConfigInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CompleteConfigInput.Marshal(b, m, deterministic) +} +func (m *CompleteConfigInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_CompleteConfigInput.Merge(m, src) +} +func (m *CompleteConfigInput) XXX_Size() int { + return xxx_messageInfo_CompleteConfigInput.Size(m) +} +func (m *CompleteConfigInput) XXX_DiscardUnknown() { + xxx_messageInfo_CompleteConfigInput.DiscardUnknown(m) +} + +var xxx_messageInfo_CompleteConfigInput proto.InternalMessageInfo + +func (m *CompleteConfigInput) GetConfigInput() []*AddConfigInput { + if m != nil { + return m.ConfigInput + } + return nil +} + type UseridByMobileParams struct { Mobile string `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"` + DomainId string `protobuf:"bytes,2,opt,name=domainId,proto3" json:"domainId,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -154,7 +326,7 @@ func (m *UseridByMobileParams) Reset() { *m = UseridByMobileParams{} } func (m *UseridByMobileParams) String() string { return proto.CompactTextString(m) } func (*UseridByMobileParams) ProtoMessage() {} func (*UseridByMobileParams) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{2} + return fileDescriptor_63fdd68f7eb311f9, []int{6} } func (m *UseridByMobileParams) XXX_Unmarshal(b []byte) error { @@ -182,6 +354,13 @@ func (m *UseridByMobileParams) GetMobile() string { return "" } +func (m *UseridByMobileParams) GetDomainId() string { + if m != nil { + return m.DomainId + } + return "" +} + type Empty struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -192,7 +371,7 @@ func (m *Empty) Reset() { *m = Empty{} } func (m *Empty) String() string { return proto.CompactTextString(m) } func (*Empty) ProtoMessage() {} func (*Empty) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{3} + return fileDescriptor_63fdd68f7eb311f9, []int{7} } func (m *Empty) XXX_Unmarshal(b []byte) error { @@ -224,7 +403,7 @@ func (m *UseridByMobileReply) Reset() { *m = UseridByMobileReply{} } func (m *UseridByMobileReply) String() string { return proto.CompactTextString(m) } func (*UseridByMobileReply) ProtoMessage() {} func (*UseridByMobileReply) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{4} + return fileDescriptor_63fdd68f7eb311f9, []int{8} } func (m *UseridByMobileReply) XXX_Unmarshal(b []byte) error { @@ -264,7 +443,7 @@ func (m *ValidateConfigReply) Reset() { *m = ValidateConfigReply{} } func (m *ValidateConfigReply) String() string { return proto.CompactTextString(m) } func (*ValidateConfigReply) ProtoMessage() {} func (*ValidateConfigReply) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{5} + return fileDescriptor_63fdd68f7eb311f9, []int{9} } func (m *ValidateConfigReply) XXX_Unmarshal(b []byte) error { @@ -299,22 +478,69 @@ func (m *ValidateConfigReply) GetMsg() string { return "" } -type BatchSendParams struct { - Contacts []string `protobuf:"bytes,1,rep,name=Contacts,proto3" json:"Contacts,omitempty"` - Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,omitempty"` - Message string `protobuf:"bytes,3,opt,name=Message,proto3" json:"Message,omitempty"` - Priority string `protobuf:"bytes,4,opt,name=Priority,proto3" json:"Priority,omitempty"` - RemoteTemplate string `protobuf:"bytes,5,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"` +type SReceiver struct { + Contact string `protobuf:"bytes,1,opt,name=Contact,proto3" json:"Contact,omitempty"` + DomainId string `protobuf:"bytes,2,opt,name=DomainId,proto3" json:"DomainId,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } +func (m *SReceiver) Reset() { *m = SReceiver{} } +func (m *SReceiver) String() string { return proto.CompactTextString(m) } +func (*SReceiver) ProtoMessage() {} +func (*SReceiver) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{10} +} + +func (m *SReceiver) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SReceiver.Unmarshal(m, b) +} +func (m *SReceiver) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SReceiver.Marshal(b, m, deterministic) +} +func (m *SReceiver) XXX_Merge(src proto.Message) { + xxx_messageInfo_SReceiver.Merge(m, src) +} +func (m *SReceiver) XXX_Size() int { + return xxx_messageInfo_SReceiver.Size(m) +} +func (m *SReceiver) XXX_DiscardUnknown() { + xxx_messageInfo_SReceiver.DiscardUnknown(m) +} + +var xxx_messageInfo_SReceiver proto.InternalMessageInfo + +func (m *SReceiver) GetContact() string { + if m != nil { + return m.Contact + } + return "" +} + +func (m *SReceiver) GetDomainId() string { + if m != nil { + return m.DomainId + } + return "" +} + +type BatchSendParams struct { + Receivers []*SReceiver `protobuf:"bytes,1,rep,name=Receivers,proto3" json:"Receivers,omitempty"` + Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,omitempty"` + Message string `protobuf:"bytes,3,opt,name=Message,proto3" json:"Message,omitempty"` + Priority string `protobuf:"bytes,4,opt,name=Priority,proto3" json:"Priority,omitempty"` + RemoteTemplate string `protobuf:"bytes,5,opt,name=RemoteTemplate,proto3" json:"RemoteTemplate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + func (m *BatchSendParams) Reset() { *m = BatchSendParams{} } func (m *BatchSendParams) String() string { return proto.CompactTextString(m) } func (*BatchSendParams) ProtoMessage() {} func (*BatchSendParams) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{6} + return fileDescriptor_63fdd68f7eb311f9, []int{11} } func (m *BatchSendParams) XXX_Unmarshal(b []byte) error { @@ -335,9 +561,9 @@ func (m *BatchSendParams) XXX_DiscardUnknown() { var xxx_messageInfo_BatchSendParams proto.InternalMessageInfo -func (m *BatchSendParams) GetContacts() []string { +func (m *BatchSendParams) GetReceivers() []*SReceiver { if m != nil { - return m.Contacts + return m.Receivers } return nil } @@ -371,18 +597,18 @@ func (m *BatchSendParams) GetRemoteTemplate() string { } type FailedRecord struct { - Contact string `protobuf:"bytes,1,opt,name=Contact,proto3" json:"Contact,omitempty"` - Reason string `protobuf:"bytes,2,opt,name=Reason,proto3" json:"Reason,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Receiver *SReceiver `protobuf:"bytes,1,opt,name=Receiver,proto3" json:"Receiver,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=Reason,proto3" json:"Reason,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *FailedRecord) Reset() { *m = FailedRecord{} } func (m *FailedRecord) String() string { return proto.CompactTextString(m) } func (*FailedRecord) ProtoMessage() {} func (*FailedRecord) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{7} + return fileDescriptor_63fdd68f7eb311f9, []int{12} } func (m *FailedRecord) XXX_Unmarshal(b []byte) error { @@ -403,11 +629,11 @@ func (m *FailedRecord) XXX_DiscardUnknown() { var xxx_messageInfo_FailedRecord proto.InternalMessageInfo -func (m *FailedRecord) GetContact() string { +func (m *FailedRecord) GetReceiver() *SReceiver { if m != nil { - return m.Contact + return m.Receiver } - return "" + return nil } func (m *FailedRecord) GetReason() string { @@ -428,7 +654,7 @@ func (m *BatchSendReply) Reset() { *m = BatchSendReply{} } func (m *BatchSendReply) String() string { return proto.CompactTextString(m) } func (*BatchSendReply) ProtoMessage() {} func (*BatchSendReply) Descriptor() ([]byte, []int) { - return fileDescriptor_63fdd68f7eb311f9, []int{8} + return fileDescriptor_63fdd68f7eb311f9, []int{13} } func (m *BatchSendReply) XXX_Unmarshal(b []byte) error { @@ -456,55 +682,156 @@ func (m *BatchSendReply) GetFailedRecords() []*FailedRecord { return nil } +type ReadyInput struct { + DomainIds []string `protobuf:"bytes,1,rep,name=DomainIds,proto3" json:"DomainIds,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ReadyInput) Reset() { *m = ReadyInput{} } +func (m *ReadyInput) String() string { return proto.CompactTextString(m) } +func (*ReadyInput) ProtoMessage() {} +func (*ReadyInput) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{14} +} + +func (m *ReadyInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ReadyInput.Unmarshal(m, b) +} +func (m *ReadyInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ReadyInput.Marshal(b, m, deterministic) +} +func (m *ReadyInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_ReadyInput.Merge(m, src) +} +func (m *ReadyInput) XXX_Size() int { + return xxx_messageInfo_ReadyInput.Size(m) +} +func (m *ReadyInput) XXX_DiscardUnknown() { + xxx_messageInfo_ReadyInput.DiscardUnknown(m) +} + +var xxx_messageInfo_ReadyInput proto.InternalMessageInfo + +func (m *ReadyInput) GetDomainIds() []string { + if m != nil { + return m.DomainIds + } + return nil +} + +type ReadyOutput struct { + Ok bool `protobuf:"varint,1,opt,name=Ok,proto3" json:"Ok,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ReadyOutput) Reset() { *m = ReadyOutput{} } +func (m *ReadyOutput) String() string { return proto.CompactTextString(m) } +func (*ReadyOutput) ProtoMessage() {} +func (*ReadyOutput) Descriptor() ([]byte, []int) { + return fileDescriptor_63fdd68f7eb311f9, []int{15} +} + +func (m *ReadyOutput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ReadyOutput.Unmarshal(m, b) +} +func (m *ReadyOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ReadyOutput.Marshal(b, m, deterministic) +} +func (m *ReadyOutput) XXX_Merge(src proto.Message) { + xxx_messageInfo_ReadyOutput.Merge(m, src) +} +func (m *ReadyOutput) XXX_Size() int { + return xxx_messageInfo_ReadyOutput.Size(m) +} +func (m *ReadyOutput) XXX_DiscardUnknown() { + xxx_messageInfo_ReadyOutput.DiscardUnknown(m) +} + +var xxx_messageInfo_ReadyOutput proto.InternalMessageInfo + +func (m *ReadyOutput) GetOk() bool { + if m != nil { + return m.Ok + } + return false +} + func init() { proto.RegisterType((*SendParams)(nil), "apis.SendParams") - proto.RegisterType((*UpdateConfigParams)(nil), "apis.UpdateConfigParams") - proto.RegisterMapType((map[string]string)(nil), "apis.UpdateConfigParams.ConfigsEntry") + proto.RegisterType((*ValidateConfigInput)(nil), "apis.ValidateConfigInput") + proto.RegisterMapType((map[string]string)(nil), "apis.ValidateConfigInput.ConfigsEntry") + proto.RegisterType((*AddConfigInput)(nil), "apis.AddConfigInput") + proto.RegisterMapType((map[string]string)(nil), "apis.AddConfigInput.ConfigsEntry") + proto.RegisterType((*UpdateConfigInput)(nil), "apis.UpdateConfigInput") + proto.RegisterMapType((map[string]string)(nil), "apis.UpdateConfigInput.ConfigsEntry") + proto.RegisterType((*DeleteConfigInput)(nil), "apis.DeleteConfigInput") + proto.RegisterType((*CompleteConfigInput)(nil), "apis.CompleteConfigInput") proto.RegisterType((*UseridByMobileParams)(nil), "apis.UseridByMobileParams") proto.RegisterType((*Empty)(nil), "apis.Empty") proto.RegisterType((*UseridByMobileReply)(nil), "apis.UseridByMobileReply") proto.RegisterType((*ValidateConfigReply)(nil), "apis.ValidateConfigReply") + proto.RegisterType((*SReceiver)(nil), "apis.SReceiver") proto.RegisterType((*BatchSendParams)(nil), "apis.BatchSendParams") proto.RegisterType((*FailedRecord)(nil), "apis.FailedRecord") proto.RegisterType((*BatchSendReply)(nil), "apis.BatchSendReply") + proto.RegisterType((*ReadyInput)(nil), "apis.ReadyInput") + proto.RegisterType((*ReadyOutput)(nil), "apis.ReadyOutput") } func init() { proto.RegisterFile("send_server.proto", fileDescriptor_63fdd68f7eb311f9) } var fileDescriptor_63fdd68f7eb311f9 = []byte{ - // 497 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xdd, 0x6a, 0xd4, 0x40, - 0x14, 0xc7, 0xc9, 0x7e, 0xef, 0xe9, 0xba, 0xd6, 0xe9, 0x5a, 0xc6, 0x5c, 0x95, 0x40, 0xa5, 0x37, - 0xe6, 0xa2, 0x22, 0x2c, 0xbd, 0xd1, 0xb6, 0xac, 0x82, 0x50, 0x28, 0xb1, 0xf5, 0x56, 0xa6, 0xc9, - 0x71, 0x1d, 0x4c, 0x32, 0x21, 0x33, 0x2d, 0xe4, 0x31, 0x7c, 0x04, 0xdf, 0x40, 0xf0, 0x05, 0x65, - 0x3e, 0xb2, 0x9b, 0xac, 0xbb, 0xed, 0x5d, 0x7e, 0xe7, 0x8b, 0x39, 0xe7, 0xff, 0x27, 0xf0, 0x42, - 0x62, 0x9e, 0x7c, 0x93, 0x58, 0x3e, 0x60, 0x19, 0x16, 0xa5, 0x50, 0x82, 0xf4, 0x58, 0xc1, 0x65, - 0xf0, 0xc7, 0x03, 0xf8, 0x82, 0x79, 0x72, 0xcd, 0x4a, 0x96, 0x49, 0x42, 0x61, 0x78, 0x29, 0x72, - 0xc5, 0x62, 0x45, 0xbd, 0x23, 0xef, 0x64, 0x1c, 0xd5, 0x48, 0x66, 0xd0, 0xbf, 0x11, 0x05, 0x8f, - 0x69, 0xc7, 0xc4, 0x2d, 0x98, 0x28, 0x57, 0x29, 0xd2, 0xae, 0x8b, 0x6a, 0xd0, 0x53, 0xae, 0x50, - 0x4a, 0xb6, 0x44, 0xda, 0xb3, 0x53, 0x1c, 0x12, 0x1f, 0x46, 0xd7, 0x25, 0x17, 0x25, 0x57, 0x15, - 0xed, 0x9b, 0xd4, 0x8a, 0xc9, 0x6b, 0x98, 0x46, 0x98, 0x09, 0x85, 0x37, 0x98, 0x15, 0x29, 0x53, - 0x48, 0x07, 0xa6, 0x62, 0x23, 0x1a, 0xfc, 0xf2, 0x80, 0xdc, 0x16, 0x09, 0x53, 0x78, 0x29, 0xf2, - 0xef, 0x7c, 0xe9, 0x9e, 0xfe, 0x1e, 0x86, 0xb1, 0x61, 0x49, 0xbd, 0xa3, 0xee, 0xc9, 0xde, 0xe9, - 0x71, 0xa8, 0x37, 0x0c, 0xff, 0x2f, 0x0d, 0x2d, 0xc8, 0x45, 0xae, 0xca, 0x2a, 0xaa, 0xbb, 0xfc, - 0x33, 0x98, 0x34, 0x13, 0x64, 0x1f, 0xba, 0x3f, 0xb1, 0x72, 0x77, 0xd0, 0x9f, 0x7a, 0xdb, 0x07, - 0x96, 0xde, 0x63, 0x7d, 0x03, 0x03, 0x67, 0x9d, 0xb9, 0x17, 0x84, 0x30, 0xbb, 0x95, 0x58, 0xf2, - 0xe4, 0xa2, 0xba, 0x12, 0x77, 0x3c, 0x45, 0xf7, 0xa8, 0x43, 0x18, 0x64, 0x86, 0xdd, 0x18, 0x47, - 0xc1, 0x10, 0xfa, 0x8b, 0xac, 0x50, 0x55, 0xf0, 0x06, 0x0e, 0xda, 0x8d, 0x11, 0x16, 0x69, 0xa5, - 0xfb, 0xee, 0x4d, 0xb8, 0xee, 0xb3, 0x14, 0x9c, 0xc3, 0xc1, 0x57, 0x96, 0xf2, 0xf5, 0x46, 0xb6, - 0x9c, 0xc2, 0x90, 0x4b, 0x93, 0x30, 0xf5, 0xa3, 0xa8, 0x46, 0xbd, 0x44, 0x26, 0x97, 0xee, 0xc1, - 0xfa, 0x33, 0xf8, 0xed, 0xc1, 0xf3, 0x0b, 0xa6, 0xe2, 0x1f, 0x0d, 0xd9, 0x7d, 0x18, 0x39, 0x9d, - 0xed, 0xf1, 0xc6, 0xd1, 0x8a, 0xd7, 0x12, 0x77, 0x76, 0x48, 0xdc, 0xdd, 0x2d, 0x71, 0xef, 0x49, - 0x89, 0xfb, 0x5b, 0x25, 0xfe, 0x00, 0x93, 0x8f, 0x8c, 0xa7, 0x98, 0x44, 0x18, 0x8b, 0x32, 0x79, - 0xc4, 0x96, 0x87, 0x30, 0x88, 0x90, 0x49, 0x91, 0xbb, 0xe7, 0x39, 0x0a, 0x3e, 0xc3, 0x74, 0xb5, - 0xa4, 0xbd, 0xd1, 0x1c, 0x9e, 0x35, 0x67, 0xd6, 0x2e, 0x21, 0xd6, 0x25, 0xcd, 0x54, 0xd4, 0x2e, - 0x3c, 0xfd, 0xdb, 0x81, 0xb1, 0x9e, 0x73, 0xbe, 0xc4, 0x5c, 0x91, 0x63, 0xe8, 0x69, 0x20, 0xfb, - 0xb6, 0x71, 0x7d, 0x45, 0x7f, 0xcf, 0x46, 0x8c, 0xb0, 0xe4, 0x1d, 0x4c, 0x9a, 0xce, 0x23, 0x74, - 0x97, 0x1b, 0xdb, 0x6d, 0x0b, 0x98, 0xb6, 0x05, 0x7e, 0xa4, 0xf1, 0x95, 0xcd, 0x6c, 0x33, 0xc4, - 0x27, 0x98, 0xb6, 0x6d, 0x45, 0x7c, 0x37, 0x66, 0x8b, 0x4b, 0xeb, 0x41, 0xdb, 0x8c, 0x38, 0x87, - 0xf1, 0xea, 0x8e, 0xe4, 0xa5, 0xad, 0xdb, 0x70, 0x8f, 0x3f, 0xdb, 0x08, 0x9b, 0xce, 0xbb, 0x81, - 0xf9, 0xcd, 0xbc, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x71, 0x6f, 0x12, 0x61, 0x7b, 0x04, 0x00, - 0x00, + // 722 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0x5d, 0x6b, 0xd4, 0x4c, + 0x14, 0x26, 0xfb, 0xd9, 0x9c, 0xed, 0xbb, 0xed, 0x4e, 0xf7, 0xed, 0x9b, 0x77, 0x51, 0xa8, 0x41, + 0x4b, 0x51, 0xbb, 0x42, 0x15, 0x59, 0x2a, 0x88, 0xdb, 0x2f, 0x69, 0xa1, 0xb4, 0x4c, 0x5b, 0x6f, + 0x65, 0xba, 0x39, 0xae, 0xa1, 0xf9, 0x22, 0x99, 0x2d, 0xe4, 0xd2, 0xdf, 0xe0, 0x6f, 0xf0, 0xca, + 0x5b, 0xff, 0x81, 0x7f, 0x4c, 0x32, 0x93, 0xc9, 0x26, 0xd9, 0x54, 0x11, 0xc1, 0xbb, 0x3c, 0x67, + 0x9e, 0x73, 0xce, 0x93, 0x93, 0x39, 0x4f, 0xa0, 0x17, 0xa1, 0x67, 0xbd, 0x8f, 0x30, 0xbc, 0xc5, + 0x70, 0x18, 0x84, 0x3e, 0xf7, 0x49, 0x83, 0x05, 0x76, 0x64, 0x7e, 0xd7, 0x00, 0x2e, 0xd0, 0xb3, + 0xce, 0x59, 0xc8, 0xdc, 0x88, 0x3c, 0x81, 0x25, 0x8a, 0x13, 0xb4, 0x6f, 0x31, 0x34, 0xb4, 0x0d, + 0x6d, 0xab, 0xb3, 0xb3, 0x32, 0x4c, 0x78, 0xc3, 0x0b, 0x15, 0xa6, 0x19, 0x81, 0xf4, 0xa1, 0x79, + 0xe9, 0x07, 0xf6, 0xc4, 0xa8, 0x6d, 0x68, 0x5b, 0x3a, 0x95, 0x40, 0x44, 0x6d, 0xee, 0xa0, 0x51, + 0x4f, 0xa3, 0x09, 0x20, 0x06, 0xb4, 0x4f, 0x31, 0x8a, 0xd8, 0x14, 0x8d, 0x86, 0x88, 0x2b, 0x48, + 0x06, 0xb0, 0x74, 0x1e, 0xda, 0x7e, 0x68, 0xf3, 0xd8, 0x68, 0x8a, 0xa3, 0x0c, 0x93, 0x4d, 0xe8, + 0x52, 0x74, 0x7d, 0x8e, 0x97, 0xe8, 0x06, 0x0e, 0xe3, 0x68, 0xb4, 0x04, 0xa3, 0x14, 0x35, 0x3f, + 0x6b, 0xb0, 0xf6, 0x8e, 0x39, 0xb6, 0xc5, 0x38, 0xee, 0xfb, 0xde, 0x07, 0x7b, 0x7a, 0xec, 0x05, + 0x33, 0x4e, 0xde, 0x40, 0x7b, 0x22, 0x60, 0x64, 0x68, 0x1b, 0xf5, 0xad, 0xce, 0xce, 0xa6, 0x7c, + 0x9b, 0x0a, 0xee, 0x50, 0x3e, 0x47, 0x87, 0x1e, 0x0f, 0x63, 0xaa, 0xd2, 0x06, 0xbb, 0xb0, 0x9c, + 0x3f, 0x20, 0xab, 0x50, 0xbf, 0xc1, 0x58, 0xcc, 0x46, 0xa7, 0xc9, 0x63, 0xf2, 0xbe, 0xb7, 0xcc, + 0x99, 0xa1, 0x9a, 0x82, 0x00, 0xbb, 0xb5, 0x91, 0x66, 0x7e, 0xd1, 0xa0, 0x3b, 0xb6, 0xac, 0xbc, + 0xa0, 0x57, 0x65, 0x41, 0x0f, 0xa4, 0xa0, 0x22, 0xad, 0x5a, 0x4b, 0x32, 0x29, 0xcb, 0x77, 0x99, + 0xed, 0x1d, 0x5b, 0x69, 0xb3, 0x0c, 0xff, 0x91, 0xce, 0xaf, 0x1a, 0xf4, 0xae, 0x82, 0xf2, 0xec, + 0x5e, 0x97, 0xa5, 0x3e, 0x94, 0x52, 0x17, 0x98, 0x7f, 0x59, 0xed, 0x33, 0xe8, 0x1d, 0xa0, 0x83, + 0x45, 0xb1, 0xf9, 0x66, 0x5a, 0xb1, 0x99, 0x79, 0x0a, 0x6b, 0xfb, 0xbe, 0x1b, 0x94, 0x53, 0x5e, + 0x42, 0x27, 0x07, 0xd3, 0x77, 0xec, 0x57, 0x7d, 0x0e, 0x9a, 0x27, 0x9a, 0x27, 0xd0, 0xbf, 0x8a, + 0x30, 0xb4, 0xad, 0xbd, 0xf8, 0xd4, 0xbf, 0xb6, 0x1d, 0x4c, 0x57, 0x67, 0x1d, 0x5a, 0xae, 0xc0, + 0xa9, 0x80, 0x14, 0xfd, 0x6c, 0x0e, 0x66, 0x1b, 0x9a, 0x87, 0x6e, 0xc0, 0x63, 0x73, 0x1b, 0xd6, + 0x8a, 0x45, 0x29, 0x06, 0x4e, 0x9c, 0xd4, 0x9c, 0x89, 0xb0, 0xaa, 0x29, 0x91, 0x39, 0x2e, 0x5f, + 0x77, 0x49, 0x37, 0xa0, 0x6d, 0x47, 0xe2, 0x40, 0xf0, 0x97, 0xa8, 0x82, 0xc9, 0x80, 0xdd, 0x68, + 0x9a, 0xf6, 0x4f, 0x1e, 0xcd, 0x31, 0xe8, 0xd9, 0x4e, 0x27, 0x89, 0xfb, 0xbe, 0xc7, 0xd9, 0x84, + 0xa7, 0x8d, 0x14, 0x4c, 0xd4, 0x1f, 0x94, 0xd4, 0x2b, 0x6c, 0x7e, 0xd3, 0x60, 0x65, 0x8f, 0xf1, + 0xc9, 0xc7, 0x9c, 0x81, 0x6c, 0x83, 0xae, 0xaa, 0xaa, 0x7b, 0xb3, 0xe0, 0x20, 0x73, 0xc6, 0xdc, + 0x2c, 0x6a, 0x77, 0x98, 0x45, 0xfd, 0x6e, 0xb3, 0x68, 0xfc, 0xd2, 0x2c, 0x9a, 0x95, 0x66, 0x71, + 0x01, 0xcb, 0x47, 0xcc, 0x76, 0xd0, 0xa2, 0x38, 0xf1, 0x43, 0xeb, 0xf7, 0x3c, 0x6f, 0x1d, 0x5a, + 0x14, 0x59, 0xe4, 0x7b, 0xa9, 0xe2, 0x14, 0x99, 0x27, 0xd0, 0xcd, 0x46, 0x21, 0x3f, 0xc6, 0x08, + 0xfe, 0xc9, 0xb7, 0x51, 0xd3, 0x20, 0xb2, 0x76, 0xfe, 0x88, 0x16, 0x89, 0xe6, 0x63, 0x00, 0x8a, + 0xcc, 0x8a, 0xe5, 0x3d, 0xbd, 0x07, 0xba, 0x9a, 0xb8, 0xac, 0xa1, 0xd3, 0x79, 0xc0, 0xbc, 0x0f, + 0x1d, 0xc1, 0x3d, 0x9b, 0xf1, 0x84, 0xdc, 0x85, 0xda, 0xd9, 0x4d, 0xfa, 0xf1, 0x6b, 0x67, 0x37, + 0x3b, 0x9f, 0x1a, 0xa0, 0x27, 0x92, 0xc6, 0x53, 0xf4, 0x38, 0x79, 0x0a, 0x4d, 0x41, 0x26, 0xab, + 0x52, 0xc4, 0xbc, 0xcb, 0xa0, 0x97, 0x8b, 0xa4, 0xb5, 0x1e, 0x41, 0x23, 0x49, 0x55, 0xe4, 0xf9, + 0x47, 0x1e, 0x74, 0x64, 0x44, 0x5c, 0x5d, 0x32, 0x04, 0x3d, 0x5b, 0x17, 0x52, 0xb9, 0x3f, 0x45, + 0xfe, 0x08, 0xba, 0xc5, 0x75, 0x24, 0xff, 0xcb, 0xe3, 0x8a, 0x25, 0x2d, 0x66, 0xbe, 0x80, 0xe5, + 0xbc, 0xf9, 0x90, 0xff, 0xee, 0x30, 0xa4, 0x85, 0xac, 0xbc, 0x5f, 0xa8, 0xac, 0x05, 0x0f, 0x29, + 0x66, 0x1d, 0x41, 0xb7, 0xb8, 0x61, 0x4a, 0x65, 0xc5, 0xaf, 0x63, 0x50, 0x79, 0x24, 0x6f, 0xc1, + 0x5b, 0xe8, 0x16, 0x17, 0x9b, 0x0c, 0x52, 0xd5, 0x15, 0x1e, 0xa2, 0x0a, 0x55, 0x59, 0xc1, 0x08, + 0xf4, 0xec, 0x82, 0x91, 0x7f, 0x25, 0xaf, 0xb4, 0x7c, 0x83, 0x7e, 0x29, 0x2c, 0x32, 0xaf, 0x5b, + 0xe2, 0x7f, 0xff, 0xfc, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x44, 0xad, 0xbd, 0x04, 0x08, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -519,9 +846,13 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type SendAgentClient interface { + Ready(ctx context.Context, in *ReadyInput, opts ...grpc.CallOption) (*ReadyOutput, error) Send(ctx context.Context, in *SendParams, opts ...grpc.CallOption) (*Empty, error) - UpdateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*Empty, error) - ValidateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*ValidateConfigReply, error) + AddConfig(ctx context.Context, in *AddConfigInput, opts ...grpc.CallOption) (*Empty, error) + CompleteConfig(ctx context.Context, in *CompleteConfigInput, opts ...grpc.CallOption) (*Empty, error) + UpdateConfig(ctx context.Context, in *UpdateConfigInput, opts ...grpc.CallOption) (*Empty, error) + DeleteConfig(ctx context.Context, in *DeleteConfigInput, opts ...grpc.CallOption) (*Empty, error) + ValidateConfig(ctx context.Context, in *ValidateConfigInput, opts ...grpc.CallOption) (*ValidateConfigReply, error) UseridByMobile(ctx context.Context, in *UseridByMobileParams, opts ...grpc.CallOption) (*UseridByMobileReply, error) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) } @@ -534,6 +865,15 @@ func NewSendAgentClient(cc *grpc.ClientConn) SendAgentClient { return &sendAgentClient{cc} } +func (c *sendAgentClient) Ready(ctx context.Context, in *ReadyInput, opts ...grpc.CallOption) (*ReadyOutput, error) { + out := new(ReadyOutput) + err := c.cc.Invoke(ctx, "/apis.SendAgent/Ready", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *sendAgentClient) Send(ctx context.Context, in *SendParams, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, "/apis.SendAgent/Send", in, out, opts...) @@ -543,7 +883,25 @@ func (c *sendAgentClient) Send(ctx context.Context, in *SendParams, opts ...grpc return out, nil } -func (c *sendAgentClient) UpdateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*Empty, error) { +func (c *sendAgentClient) AddConfig(ctx context.Context, in *AddConfigInput, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/apis.SendAgent/AddConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sendAgentClient) CompleteConfig(ctx context.Context, in *CompleteConfigInput, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/apis.SendAgent/CompleteConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sendAgentClient) UpdateConfig(ctx context.Context, in *UpdateConfigInput, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, "/apis.SendAgent/UpdateConfig", in, out, opts...) if err != nil { @@ -552,7 +910,16 @@ func (c *sendAgentClient) UpdateConfig(ctx context.Context, in *UpdateConfigPara return out, nil } -func (c *sendAgentClient) ValidateConfig(ctx context.Context, in *UpdateConfigParams, opts ...grpc.CallOption) (*ValidateConfigReply, error) { +func (c *sendAgentClient) DeleteConfig(ctx context.Context, in *DeleteConfigInput, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/apis.SendAgent/DeleteConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sendAgentClient) ValidateConfig(ctx context.Context, in *ValidateConfigInput, opts ...grpc.CallOption) (*ValidateConfigReply, error) { out := new(ValidateConfigReply) err := c.cc.Invoke(ctx, "/apis.SendAgent/ValidateConfig", in, out, opts...) if err != nil { @@ -581,9 +948,13 @@ func (c *sendAgentClient) BatchSend(ctx context.Context, in *BatchSendParams, op // SendAgentServer is the server API for SendAgent service. type SendAgentServer interface { + Ready(context.Context, *ReadyInput) (*ReadyOutput, error) Send(context.Context, *SendParams) (*Empty, error) - UpdateConfig(context.Context, *UpdateConfigParams) (*Empty, error) - ValidateConfig(context.Context, *UpdateConfigParams) (*ValidateConfigReply, error) + AddConfig(context.Context, *AddConfigInput) (*Empty, error) + CompleteConfig(context.Context, *CompleteConfigInput) (*Empty, error) + UpdateConfig(context.Context, *UpdateConfigInput) (*Empty, error) + DeleteConfig(context.Context, *DeleteConfigInput) (*Empty, error) + ValidateConfig(context.Context, *ValidateConfigInput) (*ValidateConfigReply, error) UseridByMobile(context.Context, *UseridByMobileParams) (*UseridByMobileReply, error) BatchSend(context.Context, *BatchSendParams) (*BatchSendReply, error) } @@ -592,13 +963,25 @@ type SendAgentServer interface { type UnimplementedSendAgentServer struct { } +func (*UnimplementedSendAgentServer) Ready(ctx context.Context, req *ReadyInput) (*ReadyOutput, error) { + return nil, status.Errorf(codes.Unimplemented, "method Ready not implemented") +} func (*UnimplementedSendAgentServer) Send(ctx context.Context, req *SendParams) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") } -func (*UnimplementedSendAgentServer) UpdateConfig(ctx context.Context, req *UpdateConfigParams) (*Empty, error) { +func (*UnimplementedSendAgentServer) AddConfig(ctx context.Context, req *AddConfigInput) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddConfig not implemented") +} +func (*UnimplementedSendAgentServer) CompleteConfig(ctx context.Context, req *CompleteConfigInput) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CompleteConfig not implemented") +} +func (*UnimplementedSendAgentServer) UpdateConfig(ctx context.Context, req *UpdateConfigInput) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateConfig not implemented") } -func (*UnimplementedSendAgentServer) ValidateConfig(ctx context.Context, req *UpdateConfigParams) (*ValidateConfigReply, error) { +func (*UnimplementedSendAgentServer) DeleteConfig(ctx context.Context, req *DeleteConfigInput) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteConfig not implemented") +} +func (*UnimplementedSendAgentServer) ValidateConfig(ctx context.Context, req *ValidateConfigInput) (*ValidateConfigReply, error) { return nil, status.Errorf(codes.Unimplemented, "method ValidateConfig not implemented") } func (*UnimplementedSendAgentServer) UseridByMobile(ctx context.Context, req *UseridByMobileParams) (*UseridByMobileReply, error) { @@ -612,6 +995,24 @@ func RegisterSendAgentServer(s *grpc.Server, srv SendAgentServer) { s.RegisterService(&_SendAgent_serviceDesc, srv) } +func _SendAgent_Ready_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReadyInput) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SendAgentServer).Ready(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apis.SendAgent/Ready", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SendAgentServer).Ready(ctx, req.(*ReadyInput)) + } + return interceptor(ctx, in, info, handler) +} + func _SendAgent_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendParams) if err := dec(in); err != nil { @@ -630,8 +1031,44 @@ func _SendAgent_Send_Handler(srv interface{}, ctx context.Context, dec func(inte return interceptor(ctx, in, info, handler) } +func _SendAgent_AddConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddConfigInput) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SendAgentServer).AddConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apis.SendAgent/AddConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SendAgentServer).AddConfig(ctx, req.(*AddConfigInput)) + } + return interceptor(ctx, in, info, handler) +} + +func _SendAgent_CompleteConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CompleteConfigInput) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SendAgentServer).CompleteConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apis.SendAgent/CompleteConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SendAgentServer).CompleteConfig(ctx, req.(*CompleteConfigInput)) + } + return interceptor(ctx, in, info, handler) +} + func _SendAgent_UpdateConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateConfigParams) + in := new(UpdateConfigInput) if err := dec(in); err != nil { return nil, err } @@ -643,13 +1080,31 @@ func _SendAgent_UpdateConfig_Handler(srv interface{}, ctx context.Context, dec f FullMethod: "/apis.SendAgent/UpdateConfig", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SendAgentServer).UpdateConfig(ctx, req.(*UpdateConfigParams)) + return srv.(SendAgentServer).UpdateConfig(ctx, req.(*UpdateConfigInput)) + } + return interceptor(ctx, in, info, handler) +} + +func _SendAgent_DeleteConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteConfigInput) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SendAgentServer).DeleteConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apis.SendAgent/DeleteConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SendAgentServer).DeleteConfig(ctx, req.(*DeleteConfigInput)) } return interceptor(ctx, in, info, handler) } func _SendAgent_ValidateConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateConfigParams) + in := new(ValidateConfigInput) if err := dec(in); err != nil { return nil, err } @@ -661,7 +1116,7 @@ func _SendAgent_ValidateConfig_Handler(srv interface{}, ctx context.Context, dec FullMethod: "/apis.SendAgent/ValidateConfig", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SendAgentServer).ValidateConfig(ctx, req.(*UpdateConfigParams)) + return srv.(SendAgentServer).ValidateConfig(ctx, req.(*ValidateConfigInput)) } return interceptor(ctx, in, info, handler) } @@ -706,14 +1161,30 @@ var _SendAgent_serviceDesc = grpc.ServiceDesc{ ServiceName: "apis.SendAgent", HandlerType: (*SendAgentServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "Ready", + Handler: _SendAgent_Ready_Handler, + }, { MethodName: "Send", Handler: _SendAgent_Send_Handler, }, + { + MethodName: "AddConfig", + Handler: _SendAgent_AddConfig_Handler, + }, + { + MethodName: "CompleteConfig", + Handler: _SendAgent_CompleteConfig_Handler, + }, { MethodName: "UpdateConfig", Handler: _SendAgent_UpdateConfig_Handler, }, + { + MethodName: "DeleteConfig", + Handler: _SendAgent_DeleteConfig_Handler, + }, { MethodName: "ValidateConfig", Handler: _SendAgent_ValidateConfig_Handler, diff --git a/pkg/notify/rpc/apis/send_server.proto b/pkg/notify/rpc/apis/send_server.proto index d5bc98880c..79d351182c 100644 --- a/pkg/notify/rpc/apis/send_server.proto +++ b/pkg/notify/rpc/apis/send_server.proto @@ -17,7 +17,7 @@ syntax = "proto3"; package apis; message SendParams { - string Contact = 1; + SReceiver Receiver = 1; string Topic = 2; string Title = 3; string Message = 4; @@ -25,12 +25,31 @@ message SendParams { string RemoteTemplate = 6; } -message UpdateConfigParams { +message ValidateConfigInput { map configs = 1; } +message AddConfigInput { + map configs = 1; + string domainId = 2; +} + +message UpdateConfigInput { + map configs = 1; + string domainId = 2; +} + +message DeleteConfigInput { + string domainId = 1; +} + +message CompleteConfigInput { + repeated AddConfigInput ConfigInput = 1; +} + message UseridByMobileParams { string mobile = 1; + string domainId = 2; } message Empty { @@ -45,8 +64,13 @@ message ValidateConfigReply { string msg = 2; } +message SReceiver { + string Contact = 1; + string DomainId = 2; +} + message BatchSendParams { - repeated string Contacts = 1; + repeated SReceiver Receivers = 1; string Title = 2; string Message = 3; string Priority = 4; @@ -54,7 +78,7 @@ message BatchSendParams { } message FailedRecord { - string Contact = 1; + SReceiver Receiver = 1; string Reason = 2; } @@ -62,10 +86,23 @@ message BatchSendReply { repeated FailedRecord FailedRecords = 1; } +message ReadyInput { + repeated string DomainIds = 1; +} + +message ReadyOutput { + bool Ok = 1; +} + service SendAgent { - rpc Send(SendParams) returns (Empty); - rpc UpdateConfig(UpdateConfigParams) returns (Empty); - rpc ValidateConfig(UpdateConfigParams) returns (ValidateConfigReply); - rpc UseridByMobile(UseridByMobileParams) returns (UseridByMobileReply); + rpc Ready(ReadyInput) returns (ReadyOutput); + rpc Send (SendParams) returns (Empty); + rpc AddConfig (AddConfigInput) returns (Empty); + rpc CompleteConfig (CompleteConfigInput) returns (Empty); + rpc UpdateConfig (UpdateConfigInput) returns (Empty); + rpc DeleteConfig (DeleteConfigInput) returns (Empty); + rpc ValidateConfig (ValidateConfigInput) returns (ValidateConfigReply); + rpc UseridByMobile (UseridByMobileParams) returns (UseridByMobileReply); rpc BatchSend (BatchSendParams) returns (BatchSendReply); } + diff --git a/pkg/notify/rpc/send.go b/pkg/notify/rpc/send.go index 3bae183f22..271e48e64b 100644 --- a/pkg/notify/rpc/send.go +++ b/pkg/notify/rpc/send.go @@ -34,7 +34,6 @@ import ( api "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/mcclient" notifyv2 "yunion.io/x/onecloud/pkg/notify" - "yunion.io/x/onecloud/pkg/notify/models" "yunion.io/x/onecloud/pkg/notify/rpc/apis" "yunion.io/x/onecloud/pkg/util/fileutils2" ) @@ -135,11 +134,57 @@ func (self *SRpcService) BatchSend(ctx context.Context, contactType string, args if len(args.RemoteTemplate) == 0 && contactType == api.MOBILE { return nil, fmt.Errorf("empty remote template for mobile type notification") } + domainIds := make([]string, len(args.Receivers)) + for i := range domainIds { + domainIds[i] = args.Receivers[i].DomainId + } + checks, err := self.configStore.BatchCheckConfig(contactType, domainIds) + if err != nil { + return nil, errors.Wrap(err, "BatchCheckConfig") + } + hasSystem, err := self.configStore.HasSystemConfig(contactType) + if err != nil { + return nil, errors.Wrap(err, "HasSystemConfig") + } + ret := make([]*apis.FailedRecord, 0) + receiverIndex := 0 + for i := range checks { + if checks[i] { + receiverIndex++ + continue + } + if hasSystem { + args.Receivers[receiverIndex].DomainId = "" + receiverIndex++ + continue + } + ret = append(ret, &apis.FailedRecord{ + Receiver: args.Receivers[i], + Reason: fmt.Sprintf("no %q config for in domain %q and system", contactType, domainIds[i]), + }) + args.Receivers = append(args.Receivers[:receiverIndex], args.Receivers[receiverIndex+1:]...) + } f := func(service *apis.SendNotificationClient) (interface{}, error) { + // check ready + domainIds := make([]string, len(args.Receivers)) + for i := range domainIds { + domainIds[i] = args.Receivers[i].DomainId + } + output, err := service.Ready(ctx, &apis.ReadyInput{DomainIds: domainIds}) + if err != nil { + return nil, err + } + if !output.Ok { + // if NOINIT, try to restart server and send again + service, err = self.restartService(ctx, contactType) + if err != nil { + return nil, errors.Wrapf(err, "restart service %s failed", contactType) + } + } return service.BatchSend(ctx, &args) } - ret, err := self.execute(ctx, f, contactType) + i, err := self.execute(ctx, f, contactType) if err != nil { s, ok := status.FromError(err) if !ok { @@ -147,29 +192,54 @@ func (self *SRpcService) BatchSend(ctx context.Context, contactType string, args } return nil, errors.Error(s.Message()) } - reply := ret.(*apis.BatchSendReply) - return reply.FailedRecords, nil + reply := i.(*apis.BatchSendReply) + return append(ret, reply.FailedRecords...), nil } -// RestartService can restart remote rpc server and pass config info. -// This function should be call immediately after init notify server firstly -// This function should be call immediately after accept the request about changing config. -func (self *SRpcService) RestartService(ctx context.Context, config notifyv2.SConfig, serviceName string) { - _, err := self.restartWithConfig(ctx, serviceName, config) - if err != nil { - log.Debugf("restart service failed: %s", err) +// UpdateConfig can update config for rpc service with domainId +func (self *SRpcService) UpdateConfig(ctx context.Context, service string, config notifyv2.SConfig) error { + var ( + sendService *apis.SendNotificationClient + err error + ) + + sendService, ok := self.SendServices.Get(service) + if !ok { + return fmt.Errorf("no such service %s", service) } + + args := apis.UpdateConfigInput{ + Configs: config.Config, + DomainId: config.DomainId, + } + _, err = sendService.UpdateConfig(ctx, &args) + if err != nil { + st := status.Convert(err) + if st.Code() != codes.NotFound { + return errors.Error(st.Message()) + } + _, err = sendService.AddConfig(ctx, &apis.AddConfigInput{ + Configs: config.Config, + DomainId: config.DomainId, + }) + if err != nil { + return errors.Wrap(err, "try to add config but failed") + } + } + return nil } -func (self *SRpcService) ContactByMobile(ctx context.Context, mobile, serviceName string) (string, error) { +func (self *SRpcService) ContactByMobile(ctx context.Context, mobile, serviceName string, domainId string) (string, error) { iMobile := api.ParseInternationalMobile(mobile) // compatible if iMobile.AreaCode == "86" { mobile = iMobile.Mobile } - args := apis.UseridByMobileParams{} - args.Mobile = mobile + args := apis.UseridByMobileParams{ + Mobile: mobile, + DomainId: domainId, + } f := func(service *apis.SendNotificationClient) (interface{}, error) { return service.UseridByMobile(ctx, &args) @@ -246,40 +316,47 @@ func (self *SRpcService) execute(ctx context.Context, f func(client *apis.SendNo return ret, nil } -// restartSrevice fetch config from IServiceConfigStore and Call rpc.UpdateConfig -func (self *SRpcService) restartService(ctx context.Context, serviceName string) (*apis.SendNotificationClient, error) { +var ErrGetConfig = errors.Error("Get Config Failed") - config, err := self.configStore.GetConfig(serviceName) +func (self *SRpcService) completeConfig(ctx context.Context, serviceName string, sendService *apis.SendNotificationClient) error { + // get config + configs, err := self.configStore.GetConfigs(serviceName) if err != nil { - log.Debugf("getConfig of serveice %s from database error", serviceName) - return nil, models.ErrGetConfig + log.Errorf("getConfig of serveice %s from database error", serviceName) + return ErrGetConfig } - return self.restartWithConfig(ctx, serviceName, config) + + // update config for service + configInput := make([]*apis.AddConfigInput, len(configs)) + for i := range configInput { + configInput[i] = &apis.AddConfigInput{ + Configs: configs[i].Config, + DomainId: configs[i].DomainId, + } + } + _, err = sendService.CompleteConfig(ctx, &apis.CompleteConfigInput{ + ConfigInput: configInput, + }) + if err != nil { + st := status.Convert(err) + if st.Code() == codes.FailedPrecondition { + // no such rpc serve + err = fmt.Errorf(st.Message()) + } + if st.Code() == codes.Unavailable { + err = fmt.Errorf("service is unavailable for now: %s", st.Message()) + } + return errors.Wrap(err, "UpdateConfig") + } + return nil } -func (self *SRpcService) restartWithConfig(ctx context.Context, serviceName string, - config map[string]string) (*apis.SendNotificationClient, error) { - - var ( - sendService *apis.SendNotificationClient - err error - ) - - sendService, ok := self.SendServices.Get(serviceName) - +func (self *SRpcService) restartService(ctx context.Context, service string) (*apis.SendNotificationClient, error) { + sendService, ok := self.SendServices.Get(service) if !ok { return nil, fmt.Errorf("no such service, please start new service") } - - args := apis.UpdateConfigParams{} - args.Configs = config - _, err = sendService.UpdateConfig(ctx, &args) - if err != nil { - st := status.Convert(err) - return nil, errors.Error(st.Message()) - } - - return sendService, nil + return sendService, self.completeConfig(ctx, service, sendService) } // startNewService try to start a new rpc service named serviceName @@ -308,30 +385,7 @@ func (self *SRpcService) startNewService(ctx context.Context, serviceName string return sendService, nil } - // get config - config, err := self.configStore.GetConfig(serviceName) - if err != nil { - log.Errorf("getConfig of serveice %s from database error", serviceName) - return nil, models.ErrGetConfig - } - - // update config for service - args := apis.UpdateConfigParams{} - args.Configs = config - _, err = sendService.UpdateConfig(ctx, &args) - if err != nil { - st := status.Convert(err) - if st.Code() == codes.FailedPrecondition { - // no such rpc serve - err = fmt.Errorf(st.Message()) - } - if st.Code() == codes.Unavailable { - err = fmt.Errorf("service is unavailable for now: %s", st.Message()) - } - return nil, errors.Wrap(err, "UpdateConfig") - } - - return sendService, nil + return sendService, self.completeConfig(ctx, serviceName, sendService) } // closeService will remove service record from self.SendServices and try to remove sock file @@ -374,8 +428,48 @@ func (self *SRpcService) updateService(ctx context.Context) error { return nil } -func (self *SRpcService) ValidateConfig(ctx context.Context, cType string, configs map[string]string) (isValid bool, - message string, err error) { +func (self *SRpcService) AddConfig(ctx context.Context, service string, config notifyv2.SConfig) error { + var ( + sendService *apis.SendNotificationClient + err error + ) + + sendService, ok := self.SendServices.Get(service) + if !ok { + return fmt.Errorf("no such service %s", service) + } + args := apis.AddConfigInput{ + DomainId: config.DomainId, + Configs: config.Config, + } + _, err = sendService.AddConfig(ctx, &args) + if err != nil { + return err + } + return nil +} + +func (self *SRpcService) DeleteConfig(ctx context.Context, service, domainId string) error { + var ( + sendService *apis.SendNotificationClient + err error + ) + + sendService, ok := self.SendServices.Get(service) + if !ok { + return fmt.Errorf("no such service %s", service) + } + args := apis.DeleteConfigInput{ + DomainId: domainId, + } + _, err = sendService.DeleteConfig(ctx, &args) + if err != nil { + return err + } + return nil +} + +func (self *SRpcService) ValidateConfig(ctx context.Context, cType string, configs map[string]string) (isValid bool, message string, err error) { sendService, ok := self.SendServices.Get(cType) @@ -389,7 +483,7 @@ func (self *SRpcService) ValidateConfig(ctx context.Context, cType string, confi return } } - param := apis.UpdateConfigParams{ + param := apis.ValidateConfigInput{ Configs: configs, } rep, err := sendService.ValidateConfig(ctx, ¶m) @@ -405,6 +499,44 @@ func (self *SRpcService) ValidateConfig(ctx context.Context, cType string, confi return rep.IsValid, rep.Msg, nil } +func robotType2ContactType(rType string) string { + switch rType { + case api.ROBOT_TYPE_FEISHU: + return api.FEISHU_ROBOT + case api.ROBOT_TYPE_DINGTALK: + return api.DINGTALK_ROBOT + case api.ROBOT_TYPE_WORKWX: + return api.DINGTALK_ROBOT + case api.ROBOT_TYPE_WEBHOOK: + return api.WEBHOOK + } + return rType +} + +func (self *SRpcService) SendRobotMessage(ctx context.Context, rType string, receivers []*apis.SReceiver, title string, message string) ([]*apis.FailedRecord, error) { + log.Infof("rType: %s", rType) + contactType := robotType2ContactType(rType) + args := apis.BatchSendParams{ + Receivers: receivers, + Title: title, + Message: message, + } + f := func(service *apis.SendNotificationClient) (interface{}, error) { + return service.BatchSend(ctx, &args) + } + + ret, err := self.execute(ctx, f, contactType) + if err != nil { + s, ok := status.FromError(err) + if !ok { + return nil, err + } + return nil, errors.Error(s.Message()) + } + reply := ret.(*apis.BatchSendReply) + return reply.FailedRecords, nil +} + func grpcDialWithUnixSocket(ctx context.Context, socketPath string) (*grpc.ClientConn, error) { return grpc.DialContext(ctx, socketPath, grpc.WithInsecure(), grpc.WithTimeout(time.Second*5), grpc.WithDialer( func(addr string, timeout time.Duration) (net.Conn, error) { diff --git a/pkg/notify/service/handlers.go b/pkg/notify/service/handlers.go index 4d16e2331b..89d012ed45 100644 --- a/pkg/notify/service/handlers.go +++ b/pkg/notify/service/handlers.go @@ -37,6 +37,7 @@ func InitHandlers(app *appsrv.Application) { db.TenantCacheManager, db.RoleCacheManager, models.SubContactManager, + db.SharedResourceManager, models.VerificationManager, models.SubscriptionReceiverManager, } { @@ -51,6 +52,7 @@ func InitHandlers(app *appsrv.Application) { models.ConfigManager, models.TemplateManager, models.SubscriptionManager, + models.RobotManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/notify/tasks/notifications_send_task.go b/pkg/notify/tasks/notifications_send_task.go index 2b90193839..add8bfbc1a 100644 --- a/pkg/notify/tasks/notifications_send_task.go +++ b/pkg/notify/tasks/notifications_send_task.go @@ -37,6 +37,16 @@ func (self *NotificationSendTask) taskFailed(ctx context.Context, notification * self.SetStageFailed(ctx, jsonutils.NewString(reason)) } +type DomainContact struct { + DomainId string + Contact string +} + +type ReceiverSpec struct { + receiver models.IReceiver + rNotificaion *models.SReceiverNotification +} + func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { notification := obj.(*models.SNotification) if notification.Status == apis.NOTIFICATION_STATUS_OK { @@ -50,20 +60,6 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone } notification.SetStatus(self.UserCred, apis.NOTIFICATION_STATUS_SENDING, "") - // split rns - var ( - rnsWithReceiver []*models.SReceiverNotification - rnsWithoutReceiver []*models.SReceiverNotification - ) - - for i := range rns { - if rns[i].ReceiverID == models.ReceiverIdDefault { - rnsWithoutReceiver = append(rnsWithoutReceiver, &rns[i]) - } else { - rnsWithReceiver = append(rnsWithReceiver, &rns[i]) - } - } - failedRecord := make([]string, 0) sendFail := func(rn *models.SReceiverNotification, reason string) { rn.AfterSend(ctx, false, reason) @@ -71,79 +67,80 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone } // build contactMap - contactMap := make(map[string]*models.SReceiverNotification) - contactMapEn := make(map[string]*models.SReceiverNotification) - contactmapCn := make(map[string]*models.SReceiverNotification) - for i := range rnsWithReceiver { - if len(rnsWithReceiver[i].ReceiverID) == 0 { - contactMap[rnsWithReceiver[i].Contact] = rnsWithReceiver[i] - continue - } - receiver, err := rnsWithReceiver[i].Receiver() + receivers := make([]ReceiverSpec, 0, 0) + receiversEn := make([]ReceiverSpec, 0, len(rns)/2) + receiversCn := make([]ReceiverSpec, 0, len(rns)/2) + for i := range rns { + receiver, err := rns[i].Receiver() if err != nil { - sendFail(rnsWithReceiver[i], fmt.Sprintf("fail to fetch Receiver: %s", err.Error())) + sendFail(&rns[i], fmt.Sprintf("fail to fetch Receiver: %s", err.Error())) continue } // check receiver enabled - if receiver.Enabled.IsFalse() { - sendFail(rnsWithReceiver[i], fmt.Sprintf("disabled receiver")) + if !receiver.IsEnabled() { + sendFail(&rns[i], fmt.Sprintf("disabled receiver")) continue } // check contact enabled enabled, err := receiver.IsEnabledContactType(notification.ContactType) if err != nil { - sendFail(rnsWithReceiver[i], fmt.Sprintf("IsEnabledContactType error for receiver: %s", err.Error())) + sendFail(&rns[i], fmt.Sprintf("IsEnabledContactType error for receiver: %s", err.Error())) continue } if !enabled { - sendFail(rnsWithReceiver[i], fmt.Sprintf("disabled contactType %q", notification.ContactType)) + sendFail(&rns[i], fmt.Sprintf("disabled contactType %q", notification.ContactType)) continue } // check contact verified verified, err := receiver.IsVerifiedContactType(notification.ContactType) if err != nil { - sendFail(rnsWithReceiver[i], fmt.Sprintf("IsVerifiedContactType error for receiver: %s", err.Error())) + sendFail(&rns[i], fmt.Sprintf("IsVerifiedContactType error for receiver: %s", err.Error())) continue } if !verified { - sendFail(rnsWithReceiver[i], fmt.Sprintf("unverified contactType %q", notification.ContactType)) + sendFail(&rns[i], fmt.Sprintf("unverified contactType %q", notification.ContactType)) continue } - contact, err := receiver.GetContact(notification.ContactType) - if err != nil { - reason := fmt.Sprintf("fail to fetch contact: %s", err.Error()) - sendFail(rnsWithReceiver[i], reason) - continue - } + // contact, err := receiver.GetContact(notification.ContactType) + // if err != nil { + // reason := fmt.Sprintf("fail to fetch contact: %s", err.Error()) + // sendFail(&rns[i], reason) + // continue + // } lang, err := receiver.GetTemplateLang(ctx) if err != nil { reason := fmt.Sprintf("fail to GetTemplateLang: %s", err.Error()) - sendFail(rnsWithReceiver[i], reason) + sendFail(&rns[i], reason) continue } switch lang { case "": - contactMap[contact] = rnsWithReceiver[i] + receivers = append(receivers, ReceiverSpec{ + receiver: receiver, + rNotificaion: &rns[i], + }) case apis.TEMPLATE_LANG_EN: - contactMapEn[contact] = rnsWithReceiver[i] + receiversEn = append(receiversEn, ReceiverSpec{ + receiver: receiver, + rNotificaion: &rns[i], + }) case apis.TEMPLATE_LANG_CN: - contactmapCn[contact] = rnsWithReceiver[i] + receiversCn = append(receiversCn, ReceiverSpec{ + receiver: receiver, + rNotificaion: &rns[i], + }) } } - for i := range rnsWithoutReceiver { - contactMap[rnsWithoutReceiver[i].Contact] = rnsWithoutReceiver[i] - } - var contactLen int - for lang, contactMap := range map[string]map[string]*models.SReceiverNotification{ - "": contactMap, - apis.TEMPLATE_LANG_CN: contactmapCn, - apis.TEMPLATE_LANG_EN: contactMapEn, + for lang, receivers := range map[string][]ReceiverSpec{ + "": receivers, + apis.TEMPLATE_LANG_CN: receiversCn, + apis.TEMPLATE_LANG_EN: receiversEn, } { - if len(contactMap) == 0 { + if len(receivers) == 0 { continue } @@ -154,40 +151,35 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone } // set status before send now := time.Now() - contacts := make([]string, 0, len(contactMap)) - for c, rn := range contactMap { - rn.BeforeSend(ctx, now) - contacts = append(contacts, c) + for _, rn := range receivers { + rn.rNotificaion.BeforeSend(ctx, now) } - contactLen += len(contacts) + contactLen += len(receivers) // send - fds, err := models.NotifyService.BatchSend(ctx, notification.ContactType, rpcapi.BatchSendParams{ - Contacts: contacts, - Title: p.Title, - Message: p.Message, - Priority: p.Priority, - RemoteTemplate: p.RemoteTemplate, - }) + fds, err := self.batchSend(ctx, notification.ContactType, receivers, p) if err != nil { - for _, rn := range contactMap { - sendFail(rn, err.Error()) + for _, r := range receivers { + sendFail(r.rNotificaion, err.Error()) } continue } // check result + failedRnIds := make(map[int64]struct{}, 0) for _, fd := range fds { - rn := contactMap[fd.Contact] - sendFail(rn, fd.Reason) - delete(contactMap, fd.Contact) + sendFail(fd.rNotificaion, fd.Reason) + failedRnIds[fd.rNotificaion.RowId] = struct{}{} } // after send for successful notify - for _, rn := range contactMap { - rn.AfterSend(ctx, true, "") + for _, r := range receivers { + if _, ok := failedRnIds[r.rNotificaion.RowId]; ok { + continue + } + r.rNotificaion.AfterSend(ctx, true, "") } } - if len(failedRecord) > 0 && len(failedRecord) == contactLen { + if len(failedRecord) > 0 && len(failedRecord) >= len(rns) { self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true) return } @@ -199,3 +191,83 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone logclient.AddActionLogWithContext(ctx, notification, logclient.ACT_SEND_NOTIFICATION, "", self.UserCred, true) self.SetStageComplete(ctx, nil) } + +type FailedReceiverSpec struct { + ReceiverSpec + Reason string +} + +func (self *NotificationSendTask) batchSend(ctx context.Context, contactType string, receivers []ReceiverSpec, params rpcapi.SendParams) (fails []FailedReceiverSpec, err error) { + log.Infof("contactType: %s, receivers: %s, params: %s", contactType, receivers, jsonutils.Marshal(params)) + if contactType != apis.ROBOT { + return self._batchSend(ctx, contactType, receivers, func(res []*rpcapi.SReceiver) ([]*rpcapi.FailedRecord, error) { + return models.NotifyService.BatchSend(ctx, contactType, rpcapi.BatchSendParams{ + Receivers: res, + Title: params.Title, + Message: params.Message, + Priority: params.Priority, + RemoteTemplate: params.RemoteTemplate, + }) + }) + } + robots := make(map[string][]ReceiverSpec) + for i := range receivers { + robot := receivers[i].receiver.(*models.SRobot) + robots[robot.Type] = append(robots[robot.Type], receivers[i]) + } + for rType, robots := range robots { + _fails, err := self._batchSend(ctx, contactType, robots, func(res []*rpcapi.SReceiver) ([]*rpcapi.FailedRecord, error) { + return models.NotifyService.SendRobotMessage(ctx, rType, res, params.Title, params.Message) + }) + if err != nil { + for i := range robots { + fails = append(fails, FailedReceiverSpec{ + ReceiverSpec: robots[i], + Reason: err.Error(), + }) + } + } + fails = append(fails, _fails...) + } + return fails, nil +} + +func (self *NotificationSendTask) _batchSend(ctx context.Context, contactType string, receivers []ReceiverSpec, send func([]*rpcapi.SReceiver) ([]*rpcapi.FailedRecord, error)) (fails []FailedReceiverSpec, err error) { + rpcReceivers := make([]*rpcapi.SReceiver, len(receivers)) + rpc2Receiver := make(map[DomainContact]ReceiverSpec, len(receivers)) + for i := range receivers { + contact, err := receivers[i].receiver.GetContact(contactType) + if err != nil { + fails = append(fails, FailedReceiverSpec{ + ReceiverSpec: receivers[i], + Reason: fmt.Sprintf("fail to fetch contact: %s", err.Error()), + }) + continue + } + rpcReceivers[i] = &rpcapi.SReceiver{ + DomainId: receivers[i].receiver.GetDomainId(), + Contact: contact, + } + rpc2Receiver[DomainContact{ + DomainId: receivers[i].receiver.GetDomainId(), + Contact: contact, + }] = receivers[i] + } + fds, err := send(rpcReceivers) + if err != nil { + return nil, err + } + // check result + for _, fd := range fds { + dc := DomainContact{ + DomainId: fd.Receiver.DomainId, + Contact: fd.Receiver.Contact, + } + receiver := rpc2Receiver[dc] + fails = append(fails, FailedReceiverSpec{ + ReceiverSpec: receiver, + Reason: fd.Reason, + }) + } + return +} diff --git a/pkg/notify/tasks/repull_subcontact_task.go b/pkg/notify/tasks/repull_subcontact_task.go index 7e5cc11543..961e896534 100644 --- a/pkg/notify/tasks/repull_subcontact_task.go +++ b/pkg/notify/tasks/repull_subcontact_task.go @@ -28,6 +28,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/notify/models" + "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -61,6 +62,13 @@ func (self *RepullSuncontactTask) OnInit(ctx context.Context, obj db.IStandalone } subq := models.SubContactManager.Query("receiver_id").Equals("type", config.Type).SubQuery() q := models.ReceiverManager.Query() + if config.Attribution == notify.CONFIG_ATTRIBUTION_DOMAIN { + q = q.Equals("domain_id", config.DomainId) + } else { + // The system-level config update should not affect the receiver under the domain with config + configq := models.ConfigManager.Query("domain_id").Equals("attribution", notify.CONFIG_ATTRIBUTION_DOMAIN).SubQuery() + q = q.Join(configq, sqlchemy.NotEquals(q.Field("domain_id"), configq.Field("domain_id"))) + } q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("receiver_id"))) rs := make([]models.SReceiver, 0) err := db.FetchModelObjects(models.ReceiverManager, q, &rs) diff --git a/pkg/notify/tasks/subcontact_pull_task.go b/pkg/notify/tasks/subcontact_pull_task.go index a6af78382a..8b503fe1b1 100644 --- a/pkg/notify/tasks/subcontact_pull_task.go +++ b/pkg/notify/tasks/subcontact_pull_task.go @@ -72,7 +72,7 @@ func (self *SubcontactPullTask) OnInit(ctx context.Context, obj db.IStandaloneMo if !utils.IsInStringArray(cType, PullContactType) { continue } - userid, err := models.NotifyService.ContactByMobile(ctx, receiver.Mobile, cType) + userid, err := models.NotifyService.ContactByMobile(ctx, receiver.Mobile, cType, receiver.GetDomainId()) if err != nil { var reason string if errors.Cause(err) == notify.ErrNoSuchMobile { diff --git a/pkg/notify/tasks/verification_send_task.go b/pkg/notify/tasks/verification_send_task.go index 8784af67ce..6cb53ee31e 100644 --- a/pkg/notify/tasks/verification_send_task.go +++ b/pkg/notify/tasks/verification_send_task.go @@ -12,6 +12,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" notifyv2 "yunion.io/x/onecloud/pkg/notify" "yunion.io/x/onecloud/pkg/notify/models" + "yunion.io/x/onecloud/pkg/notify/rpc/apis" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -81,7 +82,10 @@ func (self *VerificationSendTask) OnInit(ctx context.Context, obj db.IStandalone self.taskFailed(ctx, receiver, err.Error()) return } - param.Contact = contact + param.Receiver = &apis.SReceiver{ + Contact: contact, + DomainId: receiver.DomainId, + } err = models.NotifyService.Send(ctx, contactType, param) if err != nil { self.taskFailed(ctx, receiver, err.Error()) From 04d930ab2bbc4bc9e2bbbd9a6941d226b2ebb360 Mon Sep 17 00:00:00 2001 From: rainzm Date: Fri, 19 Mar 2021 11:25:31 +0800 Subject: [PATCH 2/7] feat(notify): topic and subscriber at the domain level --- cmd/climc/shell/notifyv2/subscription.go | 17 +- pkg/apis/notify/const.go | 52 +- pkg/apis/notify/subscriber.go | 87 +++ pkg/apis/notify/subscription.go | 77 --- pkg/apis/notify/topic.go | 30 + pkg/apis/notify/zz_generated.model.go | 64 ++- pkg/mcclient/modules/mod_notify.go | 33 +- pkg/mcclient/options/notify/subscription.go | 86 ++- pkg/notify/models/event_template.go | 42 +- pkg/notify/models/initdb.go | 4 +- pkg/notify/models/notification.go | 95 ++-- pkg/notify/models/subscriber.go | 443 +++++++++++++++ pkg/notify/models/subscriber_receiver.go | 80 +++ pkg/notify/models/subscription.go | 577 -------------------- pkg/notify/models/subscription_receiver.go | 201 ------- pkg/notify/models/topic.go | 403 ++++++++++++++ pkg/notify/service/handlers.go | 4 +- 17 files changed, 1256 insertions(+), 1039 deletions(-) create mode 100644 pkg/apis/notify/subscriber.go delete mode 100644 pkg/apis/notify/subscription.go create mode 100644 pkg/apis/notify/topic.go create mode 100644 pkg/notify/models/subscriber.go create mode 100644 pkg/notify/models/subscriber_receiver.go delete mode 100644 pkg/notify/models/subscription.go delete mode 100644 pkg/notify/models/subscription_receiver.go create mode 100644 pkg/notify/models/topic.go diff --git a/cmd/climc/shell/notifyv2/subscription.go b/cmd/climc/shell/notifyv2/subscription.go index cecd24f30a..2e96c305f3 100644 --- a/cmd/climc/shell/notifyv2/subscription.go +++ b/cmd/climc/shell/notifyv2/subscription.go @@ -21,10 +21,15 @@ import ( ) func init() { - cmd := shell.NewResourceCmd(&modules.NotifySubscription).WithKeyword("notify-subscription") - cmd.List(new(notify.SubscriptionListOptions)) - cmd.Show(new(notify.SubscriptionOptions)) - cmd.Perform("set-receiver", new(notify.SubscriptionSetReceiverOptions)) - cmd.Perform("set-robot", new(notify.SubscriptionSetRobotOptions)) - cmd.Perform("set-webhook", new(notify.SubscriptionSetWebhookOptions)) + cmd := shell.NewResourceCmd(&modules.NotifyTopic).WithKeyword("notify-topic") + cmd.List(new(notify.TopicListOptions)) + + cmd1 := shell.NewResourceCmd(&modules.NotifySubscriber).WithKeyword("notify-subscriber") + cmd1.List(new(notify.SubscriberListOptions)) + cmd1.Create(new(notify.SubscriberCreateOptions)) + cmd1.Show(new(notify.SubscriberOptions)) + cmd1.Delete(new(notify.SubscriberOptions)) + cmd1.Perform("set-receiver", new(notify.SubscriberSetReceiverOptions)) + cmd1.Perform("enable", new(notify.SubscriberOptions)) + cmd1.Perform("disable", new(notify.SubscriberOptions)) } diff --git a/pkg/apis/notify/const.go b/pkg/apis/notify/const.go index 89bdd3acc2..6aa6fd19f0 100644 --- a/pkg/apis/notify/const.go +++ b/pkg/apis/notify/const.go @@ -84,31 +84,33 @@ const ( RECEIVER_TYPE_CONTACT = "contact" RECEIVER_TYPE_ROBOT = "robot" - SUBSCRIPTION_RESOURCE_CREATE_DELETE = "resource create delete" - SUBSCRIPTION_RESOURCE_CHANGECONFIG = "resource change config" - SUBSCRIPTION_RESOURCE_UPDATE = "resource udpate" - SUBSCRIPTION_RESOURCE_EXPIRED_RELEASE = "resource expired release" - SUBSCRIPTION_AUTOMATED_PROCESS_EXECUTION = "automated process execution" + TOPIC_TYPE_RESOURCE = "resource" + TOPIC_TYPE_AUTOMATED_PROCESS = "automated_process" - SUBSCRIPTION_TYPE_RESOURCE = "resource" - SUBSCRIPTION_TYPE_AUTOMATED_PROCESS = "automated_process" + TOPIC_RESOURCE_SERVER = "server" + TOPIC_RESOURCE_SCALINGGROUP = "scalinggroup" + TOPIC_RESOURCE_SCALINGPOLICY = "scalingpolicy" + TOPIC_RESOURCE_IMAGE = "image" + TOPIC_RESOURCE_DISK = "disk" + TOPIC_RESOURCE_SNAPSHOT = "snapshot" + TOPIC_RESOURCE_INSTANCESNAPSHOT = "instance_snapshot" + TOPIC_RESOURCE_SNAPSHOTPOLICY = "snapshotpolicy" + TOPIC_RESOURCE_NETWORK = "network" + TOPIC_RESOURCE_EIP = "eip" + TOPIC_RESOURCE_SECGROUP = "secgroup" + TOPIC_RESOURCE_LOADBALANCER = "loadbalancer" + TOPIC_RESOURCE_LOADBALANCERACL = "loadbalanceracl" + TOPIC_RESOURCE_LOADBALANCERCERTIFICATE = "loadbalancercertificate" + TOPIC_RESOURCE_BUCKET = "bucket" + TOPIC_RESOURCE_DBINSTANCE = "dbinstance" + TOPIC_RESOURCE_ELASTICCACHE = "elasticcache" + TOPIC_RESOURCE_SCHEDULEDTASK = "scheduledtask" - SUBSCRIPTION_RESOURCE_SERVER = "server" - SUBSCRIPTION_RESOURCE_SCALINGGROUP = "scalinggroup" - SUBSCRIPTION_RESOURCE_SCALINGPOLICY = "scalingpolicy" - SUBSCRIPTION_RESOURCE_IMAGE = "image" - SUBSCRIPTION_RESOURCE_DISK = "disk" - SUBSCRIPTION_RESOURCE_SNAPSHOT = "snapshot" - SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT = "instance_snapshot" - SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY = "snapshotpolicy" - SUBSCRIPTION_RESOURCE_NETWORK = "network" - SUBSCRIPTION_RESOURCE_EIP = "eip" - SUBSCRIPTION_RESOURCE_SECGROUP = "secgroup" - SUBSCRIPTION_RESOURCE_LOADBALANCER = "loadbalancer" - SUBSCRIPTION_RESOURCE_LOADBALANCERACL = "loadbalanceracl" - SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE = "loadbalancercertificate" - SUBSCRIPTION_RESOURCE_BUCKET = "bucket" - SUBSCRIPTION_RESOURCE_DBINSTANCE = "dbinstance" - SUBSCRIPTION_RESOURCE_ELASTICCACHE = "elasticcache" - SUBSCRIPTION_RESOURCE_SCHEDULEDTASK = "scheduledtask" + SUBSCRIBER_TYPE_ROLE = "role" + SUBSCRIBER_TYPE_ROBOT = "robot" + SUBSCRIBER_TYPE_RECEIVER = "receiver" + + SUBSCRIBER_SCOPE_SYSTEM = "system" + SUBSCRIBER_SCOPE_DOMAIN = "domain" + SUBSCRIBER_SCOPE_PROJECT = "project" ) diff --git a/pkg/apis/notify/subscriber.go b/pkg/apis/notify/subscriber.go new file mode 100644 index 0000000000..bc3cf11834 --- /dev/null +++ b/pkg/apis/notify/subscriber.go @@ -0,0 +1,87 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package notify + +import "yunion.io/x/onecloud/pkg/apis" + +type SubscriberCreateInput struct { + apis.VirtualResourceCreateInput + + // description: Id of Topic + // required + TopicID string + + // description: scope of resource + // enum: system,domain,project + ResourceScope string + + // description: Type of subscriber + // enum: receiver,robot,role + Type string + + // description: receivers which is required when the type is 'receiver' will Subscribe TopicID + Receivers []string + + // description: Role(Id or Name) which is required when the type is 'role' will Subscribe TopicID + Role string + + // description: The scope of role subscribers + // enum: system,domain,project + RoleScope string + + // description: Robot(Id or Name) which is required when the type is 'robot' will Subscribe TopicID + Robot string +} + +type SubscriberListInput struct { + apis.VirtualResourceListInput + apis.EnabledResourceBaseListInput + + // description: topic id + TopicID string + + // description: scope of resource + // enum: system,domain,project + ResourceScope string + + // description: type + // enum: receiver,robot,role + Type string +} + +type Identification struct { + // example: 036fed49483b412888a760c2bc995caa + ID string `json:"id"` + // example: test + Name string `json:"name"` +} + +type SubscriberDetails struct { + apis.VirtualResourceDetails + SSubscriber + + // description: receivers + Receivers []Identification + + // description: role + Role Identification + + // description: robot + Robot Identification +} + +type SubscriberSetReceiverInput struct { + Receivers []string +} diff --git a/pkg/apis/notify/subscription.go b/pkg/apis/notify/subscription.go deleted file mode 100644 index d6d7ad9808..0000000000 --- a/pkg/apis/notify/subscription.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2019 Yunion -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package notify - -import "yunion.io/x/onecloud/pkg/apis" - -type SubscriptionListInput struct { -} - -type SubscriptionDetails struct { - apis.StatusStandaloneResourceDetails - // description: resources managed - // example: ["server", "eip", "disk"] - Resources []string `json:"resource_types"` - // description: receivers of the message sent - Receivers SubscriptionReceiver `json:"receivers"` - // description: type of robot send message - // example: dingtalk_robot - Robot string `json:"robot"` - // description: webhook send message - // example: webhook - Webhook string `json:"webhook"` -} - -type IDAndName struct { - // example: 036fed49483b412888a760c2bc995caa - ID string `json:"id"` - // example: test - Name string `json:"name"` -} - -type ReceivingRoleIDAndName struct { - IDAndName - // description: scope of role - // enum: system,domain,project - Scope string `json:"scope"` -} - -type SubscriptionReceiver struct { - Receivers []IDAndName `json:"receivers"` - ReceivingRoles []ReceivingRoleIDAndName `json:"roles"` -} - -type SubscriptionSetReceiverInput struct { - ReceivingRoles []ReceivingRole `json:"roles"` - Receivers []string `json:"receivers"` -} - -type ReceivingRole struct { - // description: id or name of role - Role string `json:"role"` - // description: scope of role - // enum: system,domain,project - Scope string `json:"scope"` -} - -type SubscriptionSetRobotInput struct { - // description: robot - // enum: feishu-robot,dingtalk-robot,workwx-robot - Robot string -} - -type SubscriptionSetWebhookInput struct { - Webhook string -} diff --git a/pkg/apis/notify/topic.go b/pkg/apis/notify/topic.go new file mode 100644 index 0000000000..7fc8f3e398 --- /dev/null +++ b/pkg/apis/notify/topic.go @@ -0,0 +1,30 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package notify + +import "yunion.io/x/onecloud/pkg/apis" + +type TopicListInput struct { + apis.StandaloneResourceListInput +} + +type TopicDetails struct { + apis.StandaloneResourceDetails + STopic + + // description: resources managed + // example: ["server", "eip", "disk"] + Resources []string `json:"resource_types"` +} diff --git a/pkg/apis/notify/zz_generated.model.go b/pkg/apis/notify/zz_generated.model.go index 7c00f13f2a..fbfbd81a7a 100644 --- a/pkg/apis/notify/zz_generated.model.go +++ b/pkg/apis/notify/zz_generated.model.go @@ -23,8 +23,10 @@ import ( // SConfig is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SConfig. type SConfig struct { apis.SStandaloneResourceBase - Type string `json:"type"` - Content interface{} `json:"content"` + apis.SDomainizedResourceBase + Type string `json:"type"` + Content interface{} `json:"content"` + Attribution string `json:"attribution"` } // SNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SNotification. @@ -69,11 +71,21 @@ type SReceiverNotification struct { NotificationID string `json:"notification_id"` // ignore if ReceiverID is not empty or default Contact string `json:"contact"` + ReceiverType string `json:"receiver_type"` SendBy string `json:"send_by"` Status string `json:"status"` FailedReason string `json:"failed_reason"` } +// SRobot is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SRobot. +type SRobot struct { + apis.SSharableVirtualResourceBase + apis.SEnabledResourceBase + Type string `json:"type"` + Address string `json:"address"` + Lang string `json:"lang"` +} + // SSubContact is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubContact. type SSubContact struct { apis.SStandaloneResourceBase @@ -87,33 +99,31 @@ type SSubContact struct { VerifiedNote string `json:"verified_note"` } -// SSubscription is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscription. -type SSubscription struct { - apis.SStandaloneResourceBase +// SSubscriber is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriber. +type SSubscriber struct { + apis.SVirtualResourceBase apis.SEnabledResourceBase - Type string `json:"type"` - Resources uint64 `json:"resources"` - Actions uint32 `json:"actions"` - AdvanceDays int `json:"advance_days"` + TopicID string `json:"topic_id"` + Type string `json:"type"` + Identification string `json:"identification"` + RoleScope string `json:"role_scope"` + ResourceScope string `json:"resource_scope"` } -// SSubscriptionReceiver is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriptionReceiver. -type SSubscriptionReceiver struct { - apis.SStandaloneResourceBase - SubscriptionID string `json:"subscription_id"` - // role id or receiver id or other and the value type is determined by the ReceiverType - Receiver string `json:"receiver"` - ReceiverType string `json:"receiver_type"` - RoleScope string `json:"role_scope"` -} - -// SSubscriptionReceiverDis is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriptionReceiverDis. -type SSubscriptionReceiverDis struct { - SSubscriptionReceiver +// SSubscriberDis is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriberDis. +type SSubscriberDis struct { + SSubscriber ReceiverName string `json:"receiver_name"` RoleName string `json:"role_name"` } +// SSubscriberReceiver is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SSubscriberReceiver. +type SSubscriberReceiver struct { + apis.SJointResourceBase + SubscriberId string `json:"subscriber_id"` + ReceiverId string `json:"receiver_id"` +} + // STemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.STemplate. type STemplate struct { apis.SStandaloneResourceBase @@ -126,6 +136,16 @@ type STemplate struct { Example string `json:"example"` } +// STopic is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.STopic. +type STopic struct { + apis.SStandaloneResourceBase + apis.SEnabledResourceBase + Type string `json:"type"` + Resources uint64 `json:"resources"` + Actions uint32 `json:"actions"` + AdvanceDays int `json:"advance_days"` +} + // SVerification is an autogenerated struct via yunion.io/x/onecloud/pkg/notify/models.SVerification. type SVerification struct { apis.SStandaloneResourceBase diff --git a/pkg/mcclient/modules/mod_notify.go b/pkg/mcclient/modules/mod_notify.go index 75ccc1fd1b..4da4f1eb44 100644 --- a/pkg/mcclient/modules/mod_notify.go +++ b/pkg/mcclient/modules/mod_notify.go @@ -21,13 +21,14 @@ type ConfigsManager struct { } var ( - NotifyReceiver modulebase.ResourceManager - NotifyConfig modulebase.ResourceManager - NotifyRobot modulebase.ResourceManager - Notification modulebase.ResourceManager - NotifyTemplate modulebase.ResourceManager - NotifySubscription modulebase.ResourceManager - Configs ConfigsManager + NotifyReceiver modulebase.ResourceManager + NotifyConfig modulebase.ResourceManager + NotifyRobot modulebase.ResourceManager + Notification modulebase.ResourceManager + NotifyTemplate modulebase.ResourceManager + NotifyTopic modulebase.ResourceManager + NotifySubscriber modulebase.ResourceManager + Configs ConfigsManager ) func init() { @@ -71,11 +72,19 @@ func init() { ) register(&NotifyTemplate) - NotifySubscription = NewNotifyv2Manager( - "subscription", - "subscriptions", - []string{"ID", "Name", "Type", "Resource_Types", "Receivers", "Robot", "Webhook"}, + NotifyTopic = NewNotifyv2Manager( + "topic", + "topics", + []string{"ID", "Name", "Type", "Enabled", "Resources"}, []string{}, ) - register(&NotifySubscription) + register(&NotifyTopic) + + NotifySubscriber = NewNotifyv2Manager( + "subscriber", + "subscribers", + []string{"ID", "Name", "Topic_Id", "Type", "Resource_Scope", "Role_Scope", "Receivers", "Role", "Robot"}, + []string{}, + ) + register(&NotifySubscriber) } diff --git a/pkg/mcclient/options/notify/subscription.go b/pkg/mcclient/options/notify/subscription.go index 121252b211..43ee4ba84f 100644 --- a/pkg/mcclient/options/notify/subscription.go +++ b/pkg/mcclient/options/notify/subscription.go @@ -14,84 +14,76 @@ package notify import ( - "fmt" - "strings" - "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/mcclient/options" ) -type SubscriptionListOptions struct { +type TopicListOptions struct { options.BaseListOptions } -func (opts *SubscriptionListOptions) Params() (jsonutils.JSONObject, error) { +func (opts *TopicListOptions) Params() (jsonutils.JSONObject, error) { return options.ListStructToParams(opts) } -type SubscriptionOptions struct { - ID string `help:"Id or Name of subscription"` +type TopicOptions struct { + ID string `help:"Id or Name of topic"` } -func (so *SubscriptionOptions) GetId() string { +func (so *TopicOptions) GetId() string { return so.ID } -func (so *SubscriptionOptions) Params() (jsonutils.JSONObject, error) { +func (so *TopicOptions) Params() (jsonutils.JSONObject, error) { return nil, nil } -type SubscriptionSetReceiverOptions struct { - SubscriptionOptions - Receivers []string `json:"receivers"` - RoleAndScopes []string `json:"role_and_scopes" help:"role and scope, separated by a colon, example: admin:system"` +type SubscriberCreateOptions struct { + Name string `positional:"true"` + TopicId string `positional:"true"` + ResourceScope string `positional:"true" choices:"system|domain|project"` + Type string `positional:"true" choices:"receiver|robot|role"` + Receivers []string `help:"required if type is 'receiver'"` + Role string `help:"required if type is 'role'"` + RoleScope string `help:"required if type if 'role'"` + Robot string `help:"required if type if 'robot'"` } -func (opts *SubscriptionSetReceiverOptions) Params() (jsonutils.JSONObject, error) { - d := jsonutils.NewDict() - if len(opts.Receivers) > 0 { - d.Set("receivers", jsonutils.NewStringArray(opts.Receivers)) - } - if len(opts.RoleAndScopes) > 0 { - ra := jsonutils.NewArray() - for _, rs := range opts.RoleAndScopes { - index := strings.Index(rs, ":") - if index <= 0 { - return nil, fmt.Errorf("invalid role_and_scope %q, example: %s", rs, "admin:system") - } - rd := jsonutils.NewDict() - rd.Set("role", jsonutils.NewString(rs[:index])) - rd.Set("scope", jsonutils.NewString(rs[index+1:])) - ra.Add(rd) - } - d.Set("roles", ra) - } - return d, nil +func (sc *SubscriberCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(sc), nil } -type SsubscriptionSetRobotOptions struct { - ROBOT string `choices:"feishu-robot|dingtalk-robot|workwx-robot"` +type SubscriberListOptions struct { + options.BaseListOptions + TopicId string + ResourceScope string `choices:"system|domain|project"` + Type string `choices:"receiver|robot|role"` } -type SubscriptionSetRobotOptions struct { - SubscriptionOptions - SsubscriptionSetRobotOptions +func (sl *SubscriberListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(sl) } -func (opts *SubscriptionSetRobotOptions) Params() (jsonutils.JSONObject, error) { - return jsonutils.Marshal(opts.SsubscriptionSetRobotOptions), nil +type SubscriberOptions struct { + ID string } -type SsubscriptionSetWebhookOptions struct { - WEBHOOK string `choices:"webhook"` +func (s *SubscriberOptions) GetId() string { + return s.ID } -type SubscriptionSetWebhookOptions struct { - SubscriptionOptions - SsubscriptionSetWebhookOptions +func (s *SubscriberOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil } -func (opts *SubscriptionSetWebhookOptions) Params() (jsonutils.JSONObject, error) { - return jsonutils.Marshal(opts.SsubscriptionSetWebhookOptions), nil +type SubscriberSetReceiverOptions struct { + SubscriberOptions + Receivers []string +} + +func (ssr *SubscriberSetReceiverOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Set("receivers", jsonutils.NewStringArray(ssr.Receivers)) + return params, nil } diff --git a/pkg/notify/models/event_template.go b/pkg/notify/models/event_template.go index c323ad6e2e..7c95d9577c 100644 --- a/pkg/notify/models/event_template.go +++ b/pkg/notify/models/event_template.go @@ -182,12 +182,12 @@ func (lt *SLocalTemplateManager) fillWithTemplate(ctx context.Context, titleOrCo } var specFields = map[string][]string{ - notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY: { + notify.TOPIC_RESOURCE_SCALINGPOLICY: { "trigger_type", "action", "unit", }, - notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK: { + notify.TOPIC_RESOURCE_SCHEDULEDTASK: { "resource_type", "operation", }, @@ -212,8 +212,8 @@ func init() { stI18nTable.Set(comapi.ST_RESOURCE_OPERATION_STOP, i18n.NewTableEntry().EN("stop").CN("关机")) stI18nTable.Set(comapi.ST_RESOURCE_OPERATION_START, i18n.NewTableEntry().EN("start").CN("开机")) - specFieldTrans[notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY] = spI18nTable - specFieldTrans[notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK] = stI18nTable + specFieldTrans[notify.TOPIC_RESOURCE_SCALINGPOLICY] = spI18nTable + specFieldTrans[notify.TOPIC_RESOURCE_SCHEDULEDTASK] = stI18nTable } func (lt *SLocalTemplateManager) getTemplate(ctx context.Context, titleOrContent string, contactType string, topic string, lang string) (*template.Template, error) { @@ -307,87 +307,87 @@ func init() { api.TEMPLATE_LANG_CN, }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_SERVER, + api.TOPIC_RESOURCE_SERVER, "virtual machine", "虚拟机", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_SCALINGGROUP, + api.TOPIC_RESOURCE_SCALINGGROUP, "scaling group", "弹性伸缩组", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_SCALINGPOLICY, + api.TOPIC_RESOURCE_SCALINGPOLICY, "scaling policy", "弹性伸缩策略", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_IMAGE, + api.TOPIC_RESOURCE_IMAGE, "image", "系统镜像", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_DISK, + api.TOPIC_RESOURCE_DISK, "disk", "硬盘", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_SNAPSHOT, + api.TOPIC_RESOURCE_SNAPSHOT, "snapshot", "硬盘快照", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT, + api.TOPIC_RESOURCE_INSTANCESNAPSHOT, "instance snapshot", "主机快照", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_NETWORK, + api.TOPIC_RESOURCE_NETWORK, "network", "IP子网", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_EIP, + api.TOPIC_RESOURCE_EIP, "EIP", "弹性公网IP", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_SECGROUP, + api.TOPIC_RESOURCE_SECGROUP, "security group", "安全组", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_LOADBALANCER, + api.TOPIC_RESOURCE_LOADBALANCER, "loadbalancer instance", "负载均衡实例", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_LOADBALANCERACL, + api.TOPIC_RESOURCE_LOADBALANCERACL, "loadbalancer ACL", "负载均衡访问控制", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE, + api.TOPIC_RESOURCE_LOADBALANCERCERTIFICATE, "loadbalancer certificate", "负载均衡证书", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_BUCKET, + api.TOPIC_RESOURCE_BUCKET, "object storage bucket", "对象存储桶", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_DBINSTANCE, + api.TOPIC_RESOURCE_DBINSTANCE, "RDS instance", "RDS实例", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_ELASTICCACHE, + api.TOPIC_RESOURCE_ELASTICCACHE, "Redis instance", "Redis实例", }, sI18nElme{ - api.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK, + api.TOPIC_RESOURCE_SCHEDULEDTASK, "scheduled task", "定时任务", }, diff --git a/pkg/notify/models/initdb.go b/pkg/notify/models/initdb.go index a5116a6f85..0dbaca87b7 100644 --- a/pkg/notify/models/initdb.go +++ b/pkg/notify/models/initdb.go @@ -32,8 +32,8 @@ func InitDB() error { ConfigManager, TemplateManager, ReceiverNotificationManager, - SubscriptionManager, - RobotManager, + TopicManager, + RobotManager, } { err := manager.InitializeData() if err != nil { diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go index a1ebc1d091..a0c1d99e72 100644 --- a/pkg/notify/models/notification.go +++ b/pkg/notify/models/notification.go @@ -232,16 +232,16 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred } // receiver - subscriptions, err := SubscriptionManager.SubsciptionByEvent(input.Event, input.AdvanceDays) + topics, err := TopicManager.TopicsByEvent(input.Event, input.AdvanceDays) if err != nil { return output, errors.Wrapf(err, "unable fetch subscriptions by event %q", input.Event) } - if len(subscriptions) == 0 { + if len(topics) == 0 { return output, nil } var receiverIds []string - for i := range subscriptions { - receiverIds1, err := SubscriptionReceiverManager.getReceivers(ctx, subscriptions[i].Id, input.ProjectDomainId, input.ProjectId) + for i := range topics { + receiverIds1, err := SubscriberManager.getReceiversSent(ctx, topics[i].Id, input.ProjectDomainId, input.ProjectId) if err != nil { return output, errors.Wrap(err, "unable to get receive") } @@ -252,14 +252,14 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred // robot var robots []string - for i := range subscriptions { - robot, err := SubscriptionReceiverManager.robot(subscriptions[i].Id) + for i := range topics { + _robots, err := SubscriberManager.robot(topics[i].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", subscriptions[i].Id) + return output, errors.Wrapf(err, "unable fetch robot of subscription %q", topics[i].Id) } } else { - robots = append(robots, robot) + robots = append(robots, _robots...) } } robots = intersection(robots, intersection(cts, RobotContactTypes)) @@ -267,22 +267,6 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred robots = sets.NewString(robots...).UnsortedList() } - // webhook - var webhooks []string - for i := range subscriptions { - webhook, err := SubscriptionReceiverManager.webhook(subscriptions[i].Id) - if err != nil { - if errors.Cause(err) != errors.ErrNotFound { - return output, errors.Wrapf(err, "unable to fetch webhook of subscription %q", subscriptions[i].Id) - } - } else { - webhooks = append(webhooks, webhook) - } - } - if len(webhooks) > 0 { - webhooks = sets.NewString(webhooks...).UnsortedList() - } - message := jsonutils.Marshal(input.ResourceDetails).String() // fillter non-existed receiver @@ -313,39 +297,56 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred } // normal contact type for _, ct := range contactTypes { - err := nm.create(ctx, userCred, ct, receiverIds, []string{}, input.Priority, "", message, input.Event, input.AdvanceDays) + err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, "", message, input.Event, input.AdvanceDays) if err != nil { output.FailedList = append(output.FailedList, api.FailedElem{ ContactType: ct, Reason: err.Error(), }) } - } // robot - for _, robot := range robots { - err := nm.create(ctx, userCred, robot, []string{}, []string{}, input.Priority, "", message, input.Event, input.AdvanceDays) - if err != nil { - output.FailedList = append(output.FailedList, api.FailedElem{ - ContactType: robot, - Reason: err.Error(), - }) - } - } - // webhook - for _, webhook := range webhooks { - err := nm.create(ctx, userCred, webhook, []string{}, []string{}, input.Priority, "", message, input.Event, input.AdvanceDays) - if err != nil { - output.FailedList = append(output.FailedList, api.FailedElem{ - ContactType: webhook, - Reason: err.Error(), - }) - } - } - + err = nm.createWithRobots(ctx, userCred, robots, input.Priority, "", message, input.Event, input.AdvanceDays) + output.FailedList = append(output.FailedList, api.FailedElem{ + ContactType: api.ROBOT, + Reason: err.Error(), + }) return output, nil } +func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds []string, priority, topic, message, event string, advanceDays int) error { + if len(robotIds) == 0 { + return nil + } + n := &SNotification{ + ContactType: api.ROBOT, + Message: message, + Priority: priority, + ReceivedAt: time.Now(), + Event: event, + AdvanceDays: advanceDays, + } + n.Id = db.DefaultUUIDGenerator() + for i := range robotIds { + _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, robotIds[i], n.Id) + if err != nil { + return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot") + } + } + err := nm.TableSpec().Insert(ctx, n) + if err != nil { + return errors.Wrap(err, "unable to insert Notification") + } + n.SetModelManager(nm, n) + task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "") + if err != nil { + log.Errorf("NotificationSendTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } + return nil +} + func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, topic, message, event string, advanceDays int) error { if len(receiverIds)+len(contacts) == 0 { log.Infof("%s: no send", contactType) @@ -374,7 +375,7 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To for i := range contacts { _, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id) if err != nil { - return errors.Wrap(err, "ReceiverNotificationManager.Create") + return errors.Wrap(err, "ReceiverNotificationManager.CreateContact") } } log.Infof("start NotificationSendTask for %s", contactType) diff --git a/pkg/notify/models/subscriber.go b/pkg/notify/models/subscriber.go new file mode 100644 index 0000000000..4ffc7a5151 --- /dev/null +++ b/pkg/notify/models/subscriber.go @@ -0,0 +1,443 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + "fmt" + "sort" + "strings" + + "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/util/sets" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +var SubscriberManager *SSubscriberManager + +func init() { + SubscriberManager = &SSubscriberManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SSubscriber{}, + "subscriber_tbl", + "subscriber", + "subscribers", + ), + } + SubscriberManager.SetVirtualObject(ReceiverNotificationManager) +} + +type SSubscriberManager struct { + db.SVirtualResourceBaseManager + db.SEnabledResourceBaseManager +} + +type SSubscriber struct { + db.SVirtualResourceBase + db.SEnabledResourceBase + + TopicID string `width:"128" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"` + Type string `width:"16" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"` + Identification string `width:"128" charset:"ascii" nullable:"false"` + RoleScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"` + ResourceScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"` +} + +func (sm *SSubscriberManager) validateReceivers(ctx context.Context, receivers []string) ([]string, error) { + rs, err := ReceiverManager.FetchByIdOrNames(ctx, receivers...) + if err != nil { + return nil, errors.Wrap(err, "unable to fetch Receivers") + } + reSet := sets.NewString(receivers...) + reIds := make([]string, len(rs)) + for i := range rs { + reSet.Delete(rs[i].GetId()) + reSet.Delete(rs[i].GetName()) + reIds[i] = rs[i].GetId() + } + if reSet.Len() > 0 { + return nil, httperrors.NewInputParameterError("receivers %q not found", strings.Join(reSet.UnsortedList(), ", ")) + } + return reIds, nil +} + +func (sm *SSubscriberManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SubscriberCreateInput) (api.SubscriberCreateInput, error) { + var err error + input.VirtualResourceCreateInput, err = sm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SVirtualResourceBaseManager.ValidateCreateData") + } + // check topic + t, err := TopicManager.FetchById(input.TopicID) + if err != nil { + return input, errors.Wrapf(err, "unable to fetch topic %s", input.TopicID) + } + // check resource scope + if !utils.IsInStringArray(input.ResourceScope, []string{api.SUBSCRIBER_SCOPE_SYSTEM, api.SUBSCRIBER_SCOPE_DOMAIN, api.SUBSCRIBER_SCOPE_PROJECT}) { + return input, httperrors.NewInputParameterError("unknown resource_scope %q", input.ResourceScope) + } + input.TopicID = t.GetId() + switch input.Type { + case api.SUBSCRIBER_TYPE_RECEIVER: + reIds, err := sm.validateReceivers(ctx, input.Receivers) + if err != nil { + return input, err + } + input.Receivers = reIds + case api.SUBSCRIBER_TYPE_ROLE: + if input.RoleScope == "" { + input.RoleScope = input.ResourceScope + } + roleCache, err := db.RoleCacheManager.FetchRoleByIdOrName(ctx, input.Role) + if err != nil { + return input, errors.Wrapf(err, "unable find role %q", input.Role) + } + input.Role = roleCache.GetId() + case api.SUBSCRIBER_TYPE_ROBOT: + robot, err := RobotManager.FetchByIdOrName(userCred, input.Robot) + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewInputParameterError("robot %q not found", input.Robot) + } + if err != nil { + return input, errors.Wrapf(err, "unable to fetch robot %q", input.Robot) + } + input.Robot = robot.GetId() + default: + return input, httperrors.NewInputParameterError("unkown type %q", input.Type) + } + return input, nil +} + +func (s *SSubscriber) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + err := s.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) + if err != nil { + return errors.Wrap(err, "SVirtualResourceBase.CustomizeCreate") + } + var input api.SubscriberCreateInput + _ = data.Unmarshal(&input) + switch input.Type { + case api.SUBSCRIBER_TYPE_RECEIVER: + err := s.SetReceivers(ctx, input.Receivers) + if err != nil { + return errors.Wrapf(err, "unable to set connect receivers with subscriber %s", s.Id) + } + case api.SUBSCRIBER_TYPE_ROBOT: + s.Identification = input.Robot + case api.SUBSCRIBER_TYPE_ROLE: + s.Identification = input.Role + } + return nil +} + +func (sm *SSubscriberManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.SubscriberListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = sm.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter") + } + q, err = sm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledResourceBaseManager.ListItemFilter") + } + if input.TopicID != "" { + q = q.Equals("topic_id", input.TopicID) + } + if input.Type != "" { + q = q.Equals("type", input.Type) + } + if input.ResourceScope != "" { + q = q.Equals("resource_scope", input.ResourceScope) + } + return q, nil +} + +func (sm *SSubscriberManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.SubscriberDetails { + var err error + vRows := sm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + rows := make([]api.SubscriberDetails, len(objs)) + for i := range rows { + rows[i].VirtualResourceDetails = vRows[i] + s := objs[i].(*SSubscriber) + switch s.Type { + case api.SUBSCRIBER_TYPE_RECEIVER: + rows[i].Receivers, err = s.receiverIdentifications() + if err != nil { + log.Errorf("unable to get receiverIdentifications for subscriber %q: %v", s.Id, err) + } + case api.SUBSCRIBER_TYPE_ROBOT: + rows[i].Robot, err = s.robotIdentification() + if err != nil { + log.Errorf("unable get robotIdentification for subscriber %q: %v", s.Id, err) + } + case api.SUBSCRIBER_TYPE_ROLE: + rows[i].Role, err = s.roleIdentification() + if err != nil { + log.Errorf("unable to get roleIdentification for subscriber %q: %v", s.Id, err) + } + } + } + return rows +} + +func (s *SSubscriber) receiverIdentifications() ([]api.Identification, error) { + srSubq := SubscriberReceiverManager.Query().Equals("subscription_id", s.Id).SubQuery() + rq := ReceiverManager.Query("id", "name") + rq = rq.Join(srSubq, sqlchemy.Equals(srSubq.Field("receiver_id"), rq.Field("id"))) + var ret []api.Identification + err := rq.All(&ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (s *SSubscriber) robotIdentification() (api.Identification, error) { + var ret api.Identification + q := RobotManager.Query("id", "name").Equals("id", s.Identification) + err := q.First(&ret) + if err != nil { + return ret, err + } + return ret, nil +} + +func (s *SSubscriber) roleIdentification() (api.Identification, error) { + var ret api.Identification + q := db.RoleCacheManager.Query("id", "name").Equals("id", s.Identification) + err := q.First(&ret) + if err != nil { + return ret, err + } + return ret, nil +} + +func (srm *SSubscriberManager) robot(tid, projectDomainId, projectId string) ([]string, error) { + srs, err := srm.findSuitableOnes(tid, projectDomainId, projectId, api.SUBSCRIBER_TYPE_ROBOT) + if err != nil { + return nil, err + } + robotIds := make([]string, len(srs)) + for i := range srs { + robotIds[i] = srs[i].Identification + } + return robotIds, nil +} + +func (srm *SSubscriberManager) findSuitableOnes(tid, projectDomainId, projectId string, types ...string) ([]SSubscriber, error) { + q := srm.Query().Equals("subscription_id", tid) + q = q.Filter(sqlchemy.OR( + sqlchemy.AND( + sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_PROJECT), + sqlchemy.Equals(q.Field("project_id"), projectId), + ), + sqlchemy.AND( + sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_DOMAIN), + sqlchemy.Equals(q.Field("domain_id"), projectDomainId), + ), + sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_SYSTEM), + )) + switch len(types) { + case 0: + case 1: + q = q.Equals("type", types[0]) + default: + q = q.In("type", types) + } + srs := make([]SSubscriber, 0, 1) + err := db.FetchModelObjects(srm, q, &srs) + if err != nil { + return nil, err + } + return srs, nil +} + +// TODO: Use cache to increase speed +func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, projectDomainId string, projectId string) ([]string, 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)) + roleMap := make(map[string][]string, 3) + receivermap := make(map[string]*[]string, 3) + 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...) + } else if sr.Type == api.SUBSCRIBER_TYPE_ROBOT { + roleMap[sr.RoleScope] = append(roleMap[sr.RoleScope], sr.Identification) + receivermap[sr.RoleScope] = &[]string{} + } + } + 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)) + query.Set("effective", jsonutils.JSONTrue) + switch scope { + case api.SUBSCRIBER_SCOPE_SYSTEM: + case api.SUBSCRIBER_SCOPE_DOMAIN: + if len(projectDomainId) == 0 { + return fmt.Errorf("need projectDomainId") + } + query.Set("project_domain_id", jsonutils.NewString(projectDomainId)) + case api.SUBSCRIBER_SCOPE_PROJECT: + if len(projectId) == 0 { + return fmt.Errorf("need projectId") + } + query.Add(jsonutils.NewString(projectId), "scope", "project", "id") + } + s := auth.GetAdminSession(ctx, "", "") + log.Debugf("query for role-assignments: %s", query.String()) + listRet, err := modules.RoleAssignments.List(s, query) + if err != nil { + return errors.Wrap(err, "unable to list RoleAssignments") + } + log.Debugf("return value for role-assignments: %s", jsonutils.Marshal(listRet)) + for i := range listRet.Data { + ras := listRet.Data[i] + user, err := ras.Get("user") + if err == nil { + id, err := user.GetString("id") + if err != nil { + return errors.Wrap(err, "unable to get user.id from result of RoleAssignments.List") + } + *receivers = append(*receivers, id) + } + } + return nil + }) + } + err = errgo.Wait() + if err != nil { + return nil, err + } + for _, res := range receivermap { + receivers = append(receivers, *res...) + } + // de-duplication + return sets.NewString(receivers...).UnsortedList(), nil +} + +func (sr *SSubscriber) getReceivers() ([]string, error) { + srrs, err := SubscriberReceiverManager.getBySubscriberId(sr.Id) + if err != nil { + return nil, err + } + rIds := make([]string, len(srrs)) + for i := range srrs { + rIds[i] = srrs[i].ReceiverId + } + return rIds, nil +} + +func (sr *SSubscriber) SetReceivers(ctx context.Context, receiverIds []string) error { + srrs, err := SubscriberReceiverManager.getBySubscriberId(sr.Id) + if err != nil { + return errors.Wrapf(err, "unable to get SRReceiver by Subscriber %s", sr.Id) + } + dbReceivers := make([]string, len(srrs)) + for i := range srrs { + dbReceivers[i] = srrs[i].ReceiverId + } + var addReceivers, rmReceivers []string + sort.Strings(dbReceivers) + sort.Strings(receiverIds) + for i, j := 0, 0; i < len(dbReceivers) || j < len(receiverIds); { + switch { + case i == len(dbReceivers): + addReceivers = append(addReceivers, receiverIds[j]) + j++ + case j == len(receiverIds): + rmReceivers = append(rmReceivers, dbReceivers[i]) + i++ + case dbReceivers[i] > receiverIds[j]: + rmReceivers = append(rmReceivers, dbReceivers[i]) + i++ + case dbReceivers[i] < receiverIds[j]: + addReceivers = append(addReceivers, receiverIds[j]) + j++ + case dbReceivers[i] == receiverIds[j]: + } + } + // add + for _, rId := range addReceivers { + _, err := SubscriberReceiverManager.create(ctx, sr.Id, rId) + if err != nil { + return errors.Wrapf(err, "unable to connect subscription receiver %q with receiver %q", sr.Id, rId) + } + } + for _, rId := range rmReceivers { + err := SubscriberReceiverManager.delete(sr.Id, rId) + if err != nil { + return errors.Wrapf(err, "unable to disconnect subscription receiver %q with receiver %q", sr.Id, rId) + } + } + return nil +} + +func (s *SSubscriber) AllowPerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return s.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, s, "set-receiver") +} + +func (s *SSubscriber) PerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SubscriberSetReceiverInput) (jsonutils.JSONObject, error) { + reIds, err := SubscriberManager.validateReceivers(ctx, input.Receivers) + if err != nil { + return nil, err + } + return nil, s.SetReceivers(ctx, reIds) +} + +func (s *SSubscriber) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, s, "enable") +} + +func (s *SSubscriber) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + err := db.EnabledPerformEnable(s, ctx, userCred, true) + if err != nil { + return nil, errors.Wrap(err, "EnabledPerformEnable") + } + return nil, nil +} + +func (s *SSubscriber) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, s, "disable") +} + +func (s *SSubscriber) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + err := db.EnabledPerformEnable(s, ctx, userCred, false) + if err != nil { + return nil, errors.Wrap(err, "EnabledPerformEnable") + } + return nil, nil +} diff --git a/pkg/notify/models/subscriber_receiver.go b/pkg/notify/models/subscriber_receiver.go new file mode 100644 index 0000000000..f7927b48fe --- /dev/null +++ b/pkg/notify/models/subscriber_receiver.go @@ -0,0 +1,80 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" +) + +var SubscriberReceiverManager *SSubscriberReceiverManager + +func init() { + SubscriberReceiverManager = &SSubscriberReceiverManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + SSubscriberReceiver{}, + "subscriber_receiver_tbl", + "subscriber_receiver", + "subscriber_receivers", + SubscriberManager, + ReceiverManager, + ), + } + SubscriberReceiverManager.SetVirtualObject(SubscriberReceiverManager) +} + +type SSubscriberReceiverManager struct { + db.SJointResourceBaseManager +} + +type SSubscriberReceiver struct { + db.SJointResourceBase + SubscriberId string `width:"36" charset:"ascii" nullable:"false" index:"true"` + ReceiverId string `width:"36" charset:"ascii" nullable:"false" index:"true"` +} + +func (srm *SSubscriberReceiverManager) getBySubscriberId(sId string) ([]SSubscriberReceiver, error) { + q := srm.Query().Equals("subscriber_id", sId) + srs := make([]SSubscriberReceiver, 0, 2) + err := db.FetchModelObjects(srm, q, &srs) + if err != nil { + return nil, err + } + return srs, nil +} + +func (srm *SSubscriberReceiverManager) create(ctx context.Context, sId, receiverId string) (*SSubscriberReceiver, error) { + sr := &SSubscriberReceiver{ + SubscriberId: sId, + ReceiverId: receiverId, + } + return sr, srm.TableSpec().Insert(ctx, sr) +} + +func (srm *SSubscriberReceiverManager) delete(sId, receiverId string) error { + q := srm.Query().Equals("receiver_id", receiverId) + var sr SSubscriberReceiver + err := q.First(&sr) + if err != nil { + return err + } + srp := &sr + _, err = db.Update(srp, func() error { + srp.MarkDelete() + return nil + }) + return err +} diff --git a/pkg/notify/models/subscription.go b/pkg/notify/models/subscription.go deleted file mode 100644 index 876475e08a..0000000000 --- a/pkg/notify/models/subscription.go +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright 2019 Yunion -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package models - -import ( - "context" - "fmt" - "strings" - - "golang.org/x/sync/errgroup" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/sets" - "yunion.io/x/pkg/utils" - "yunion.io/x/sqlchemy" - - "yunion.io/x/onecloud/pkg/apis/notify" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/util/bitmap" - "yunion.io/x/onecloud/pkg/util/rbacutils" - "yunion.io/x/onecloud/pkg/util/stringutils2" -) - -func parseEvent(es string) (notify.SEvent, error) { - ess := strings.Split(es, notify.DelimiterInEvent) - if len(ess) != 2 { - return notify.SEvent{}, fmt.Errorf("invalid event string %q", es) - } - return notify.Event.WithResourceType(ess[0]).WithAction(notify.SAction(ess[1])), nil -} - -type SSubscriptionManager struct { - db.SStandaloneResourceBaseManager - db.SEnabledResourceBaseManager -} - -var SubscriptionManager *SSubscriptionManager - -func init() { - SubscriptionManager = &SSubscriptionManager{ - SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( - SSubscription{}, - "subscription_tbl", - "subscription", - "subscriptions", - ), - } - SubscriptionManager.SetVirtualObject(SubscriptionManager) -} - -type SSubscription struct { - db.SStandaloneResourceBase - db.SEnabledResourceBase - - Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"` - Resources uint64 `nullable:"false"` - Actions uint32 `nullable:"false"` - AdvanceDays int `nullable:"false"` -} - -const ( - DefaultResourceCreateDelete = "resource create or delete" - DefaultResourceChangeConfig = "resource change config" - DefaultResourceUpdate = "resource update" - DefaultResourceReleaseDue1Day = "resource release due 1 day" - DefaultResourceReleaseDue3Day = "resource release due 3 day" - DefaultScheduledTaskExecute = "scheduled task execute" - DefaultScalingPolicyExecute = "scaling policy execute" - DefaultSnapshotPolicyExecute = "snapshot policy execute" -) - -func (sm *SSubscriptionManager) InitializeData() error { - initSNames := sets.NewString( - DefaultResourceCreateDelete, - DefaultResourceChangeConfig, - DefaultResourceUpdate, - DefaultResourceReleaseDue1Day, - DefaultResourceReleaseDue3Day, - DefaultScheduledTaskExecute, - DefaultScalingPolicyExecute, - DefaultSnapshotPolicyExecute, - ) - q := sm.Query() - subscriptions := make([]SSubscription, 0, initSNames.Len()) - err := db.FetchModelObjects(sm, q, &subscriptions) - if err != nil { - return errors.Wrap(err, "unable to FetchModelObjects") - } - for i := range subscriptions { - ss := &subscriptions[i] - initSNames.Delete(ss.Name) - } - ctx := context.Background() - for _, name := range initSNames.UnsortedList() { - ss := new(SSubscription) - ss.Name = name - switch name { - case DefaultResourceCreateDelete: - ss.addResources( - notify.SUBSCRIPTION_RESOURCE_SERVER, - notify.SUBSCRIPTION_RESOURCE_SCALINGGROUP, - notify.SUBSCRIPTION_RESOURCE_IMAGE, - notify.SUBSCRIPTION_RESOURCE_DISK, - notify.SUBSCRIPTION_RESOURCE_SNAPSHOT, - notify.SUBSCRIPTION_RESOURCE_INSTANCESNAPSHOT, - notify.SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY, - notify.SUBSCRIPTION_RESOURCE_NETWORK, - notify.SUBSCRIPTION_RESOURCE_EIP, - notify.SUBSCRIPTION_RESOURCE_LOADBALANCER, - notify.SUBSCRIPTION_RESOURCE_LOADBALANCERACL, - notify.SUBSCRIPTION_RESOURCE_LOADBALANCERCERTIFICATE, - notify.SUBSCRIPTION_RESOURCE_BUCKET, - notify.SUBSCRIPTION_RESOURCE_DBINSTANCE, - notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE, - ) - ss.addAction( - notify.ActionCreate, - notify.ActionDelete, - notify.ActionPendingDelete, - ) - ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE - case DefaultResourceChangeConfig: - ss.addResources( - notify.SUBSCRIPTION_RESOURCE_SERVER, - notify.SUBSCRIPTION_RESOURCE_DISK, - notify.SUBSCRIPTION_RESOURCE_DBINSTANCE, - notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE, - ) - ss.addAction(notify.ActionChangeConfig) - ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE - case DefaultResourceUpdate: - ss.addResources( - notify.SUBSCRIPTION_RESOURCE_SERVER, - notify.SUBSCRIPTION_RESOURCE_DISK, - notify.SUBSCRIPTION_RESOURCE_DBINSTANCE, - notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE, - ) - ss.addAction(notify.ActionUpdate) - ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE - case DefaultResourceReleaseDue1Day: - ss.addResources( - notify.SUBSCRIPTION_RESOURCE_SERVER, - notify.SUBSCRIPTION_RESOURCE_DISK, - notify.SUBSCRIPTION_RESOURCE_EIP, - notify.SUBSCRIPTION_RESOURCE_LOADBALANCER, - notify.SUBSCRIPTION_RESOURCE_DBINSTANCE, - notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE, - ) - ss.addAction(notify.ActionExpiredRelease) - ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE - ss.AdvanceDays = 1 - case DefaultResourceReleaseDue3Day: - ss.addResources( - notify.SUBSCRIPTION_RESOURCE_SERVER, - notify.SUBSCRIPTION_RESOURCE_DISK, - notify.SUBSCRIPTION_RESOURCE_EIP, - notify.SUBSCRIPTION_RESOURCE_LOADBALANCER, - notify.SUBSCRIPTION_RESOURCE_DBINSTANCE, - notify.SUBSCRIPTION_RESOURCE_ELASTICCACHE, - ) - ss.addAction(notify.ActionExpiredRelease) - ss.Type = notify.SUBSCRIPTION_TYPE_RESOURCE - ss.AdvanceDays = 3 - case DefaultScheduledTaskExecute: - ss.addResources(notify.SUBSCRIPTION_RESOURCE_SCHEDULEDTASK) - ss.addAction(notify.ActionExecute) - ss.Type = notify.SUBSCRIPTION_TYPE_AUTOMATED_PROCESS - case DefaultScalingPolicyExecute: - ss.addResources(notify.SUBSCRIPTION_RESOURCE_SCALINGPOLICY) - ss.addAction(notify.ActionExecute) - ss.Type = notify.SUBSCRIPTION_TYPE_AUTOMATED_PROCESS - case DefaultSnapshotPolicyExecute: - ss.addResources(notify.SUBSCRIPTION_RESOURCE_SNAPSHOTPOLICY) - ss.addAction(notify.ActionExecute) - ss.Type = notify.SUBSCRIPTION_TYPE_AUTOMATED_PROCESS - } - err := sm.TableSpec().Insert(ctx, ss) - if err != nil { - return errors.Wrapf(err, "unable to insert %s", name) - } - } - return nil -} - -func (sm *SSubscriptionManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input notify.SubscriptionListInput) (*sqlchemy.SQuery, error) { - return q, nil -} - -func (sm *SSubscriptionManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []notify.SubscriptionDetails { - sRows := sm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) - rows := make([]notify.SubscriptionDetails, len(objs)) - for i := range rows { - rows[i].StandaloneResourceDetails = sRows[i] - ss := objs[i].(*SSubscription) - rows[i].Resources = ss.getResources() - srs, err := ss.subscriptionReceiverDiss() - if err != nil { - log.Errorf("unable to get subscriptionReceivers: %v", err) - } - for j := range srs { - sr := &srs[j] - switch sr.ReceiverType { - case ReceiverNormal: - rows[i].Receivers.Receivers = append(rows[i].Receivers.Receivers, notify.IDAndName{ - ID: sr.Receiver, - Name: sr.ReceiverName, - }) - case ReceiverRole: - rows[i].Receivers.ReceivingRoles = append(rows[i].Receivers.ReceivingRoles, notify.ReceivingRoleIDAndName{ - IDAndName: notify.IDAndName{ - ID: sr.Receiver, - Name: sr.RoleName, - }, - Scope: sr.RoleScope, - }) - case ReceiverDingtalkRobot, ReceiverFeishuRobot, ReceiverWorkwxRobot: - rows[i].Robot = sr.ReceiverType - case ReceiverWeebhook: - rows[i].Webhook = sr.ReceiverType - } - } - } - return rows -} - -type SSubscriptionReceiverDis struct { - SSubscriptionReceiver - ReceiverName string `json:"receiver_name"` - RoleName string `json:"role_name"` -} - -func (s *SSubscription) subscriptionReceiverDiss() ([]SSubscriptionReceiverDis, error) { - q := SubscriptionReceiverManager.Query().Equals("subscription_id", s.Id) - rq := ReceiverManager.Query("id", "name").SubQuery() - roq := db.RoleCacheManager.Query("id", "name").SubQuery() - q = q.LeftJoin(rq, sqlchemy.Equals(q.Field("receiver"), rq.Field("id"))) - q = q.LeftJoin(roq, sqlchemy.Equals(q.Field("receiver"), roq.Field("id"))) - // It looks strange, but the order of append cannot be changed - q.AppendField(q.QueryFields()...) - q.AppendField(rq.Field("name", "receiver_name")) - q.AppendField(roq.Field("name", "role_name")) - srs := make([]SSubscriptionReceiverDis, 0) - err := q.All(&srs) - if err != nil { - return nil, errors.Wrap(err, "unable to fetch All") - } - return srs, nil -} - -func (sm *SSubscriptionManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { - return nil, httperrors.NewForbiddenError("prohibit creation") -} - -func (ss *SSubscription) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { - return input, httperrors.NewForbiddenError("update prohibited") -} - -func (ss *SSubscription) ValidateDeleteCondition(ctx context.Context) error { - return httperrors.NewForbiddenError("prohibit deletion") -} - -func (ss *SSubscription) AllowPerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return db.IsAdminAllowPerform(userCred, ss, "set-receiver") -} - -func (ss *SSubscription) PerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input notify.SubscriptionSetReceiverInput) (jsonutils.JSONObject, error) { - // check input - errgo, _ := errgroup.WithContext(ctx) - // check receiving roles - validScopes := []string{string(rbacutils.ScopeSystem), string(rbacutils.ScopeDomain), string(rbacutils.ScopeProject)} - for i := range input.ReceivingRoles { - role := input.ReceivingRoles[i] - index := i - errgo.Go(func() error { - if len(role.Scope) == 0 { - return httperrors.NewInputParameterError("empty scope for role %q", role.Role) - } - if !utils.IsInStringArray(role.Scope, validScopes) { - return httperrors.NewInputParameterError("invalid scope %q for role %q, need %s, %s or %s", role.Scope, role.Role, rbacutils.ScopeSystem, rbacutils.ScopeDomain, rbacutils.ScopeProject) - } - roleCache, err := db.RoleCacheManager.FetchRoleByIdOrName(ctx, role.Role) - if err != nil { - return errors.Wrapf(err, "unable find role %q", role) - } - input.ReceivingRoles[index].Role = roleCache.GetId() - return nil - }) - } - err := errgo.Wait() - if err != nil { - return nil, err - } - receivers, err := ReceiverManager.FetchByIdOrNames(ctx, input.Receivers...) - if err != nil { - return nil, errors.Wrap(err, "unable to fetch Receivers") - } - reSet := sets.NewString(input.Receivers...) - reIds := make([]string, len(receivers)) - for i := range receivers { - reSet.Delete(receivers[i].GetId()) - reSet.Delete(receivers[i].GetName()) - reIds[i] = receivers[i].GetId() - } - if reSet.Len() > 0 { - return nil, httperrors.NewInputParameterError("receivers %q not found", strings.Join(reSet.UnsortedList(), ", ")) - } - input.Receivers = reIds - - // deal with subscriptionReceivers - srs, err := SubscriptionReceiverManager.findReceivers(ss.Id, ReceiverNormal, ReceiverRole) - if err != nil { - return nil, errors.Wrap(err, "unable to findReceivers") - } - reSet = sets.NewString(input.Receivers...) - reRoleSet := make(map[notify.ReceivingRole]struct{}) - for _, role := range input.ReceivingRoles { - reRoleSet[role] = struct{}{} - } - for i := range srs { - rs := &srs[i] - switch rs.ReceiverType { - case ReceiverNormal: - if !reSet.Has(rs.Receiver) { - err := rs.Delete(ctx, userCred) - if err != nil { - return nil, errors.Wrapf(err, "unable to delete receiver %s", rs.Receiver) - } - } - reSet.Delete(rs.Receiver) - case ReceiverRole: - role := rs.receivingRole() - if _, ok := reRoleSet[role]; !ok { - err := rs.Delete(ctx, userCred) - if err != nil { - return nil, errors.Wrapf(err, "unable to delete receiver %s", rs.Receiver) - } - } - delete(reRoleSet, role) - } - } - for _, re := range reSet.UnsortedList() { - _, err := SubscriptionReceiverManager.create(ctx, ss.Id, re, ReceiverNormal, "") - if err != nil { - return nil, errors.Wrapf(err, "unable to create receiver %s", re) - } - } - for role := range reRoleSet { - _, err := SubscriptionReceiverManager.create(ctx, ss.Id, role.Role, ReceiverRole, role.Scope) - if err != nil { - return nil, errors.Wrapf(err, "unable to create role %s", role) - } - } - - return nil, nil -} - -func (ss *SSubscription) AllowPerformSetRobot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return db.IsAdminAllowPerform(userCred, ss, "set-robot") -} - -func (ss *SSubscription) PerformSetRobot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input notify.SubscriptionSetRobotInput) (jsonutils.JSONObject, error) { - err := ss.setSingleReceiver(ctx, input.Robot, ReceiverDingtalkRobot, ReceiverFeishuRobot, ReceiverWorkwxRobot) - if errors.Cause(err) == errors.ErrNotFound { - return nil, httperrors.NewInputParameterError("unkown robot %q", input.Robot) - } - return nil, err -} - -func (ss *SSubscription) AllowPerformSetWebhook(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return db.IsAdminAllowPerform(userCred, ss, "set-webhook") -} - -func (ss *SSubscription) PerformSetWebhook(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input notify.SubscriptionSetWebhookInput) (jsonutils.JSONObject, error) { - err := ss.setSingleReceiver(ctx, input.Webhook, ReceiverWeebhook) - if errors.Cause(err) == errors.ErrNotFound { - return nil, httperrors.NewInputParameterError("unkown webhook %q", input.Webhook) - } - return nil, err -} - -func (ss *SSubscription) setSingleReceiver(ctx context.Context, re string, reTypes ...string) error { - receiverRobot := reTypes - if !utils.IsInStringArray(re, receiverRobot) { - return errors.ErrNotFound - } - srs, err := SubscriptionReceiverManager.findReceivers(ss.Id, receiverRobot...) - if err != nil { - return errors.Wrap(err, "unable to findReceivers") - } - if len(srs) == 0 { - // create one - _, err := SubscriptionReceiverManager.create(ctx, ss.Id, "", re, "") - return err - } - if len(srs) > 1 { - return fmt.Errorf("multi robot receiver") - } - // update - sr := &srs[0] - _, err = db.Update(sr, func() error { - sr.ReceiverType = re - return nil - }) - return err -} - -func (s *SSubscription) addResources(resources ...string) { - for _, resource := range resources { - v := converter.resourceValue(resource) - if v < 0 { - continue - } - s.Resources += 1 << v - } -} - -func (s *SSubscription) addAction(actions ...notify.SAction) { - for _, action := range actions { - v := converter.actionValue(action) - if v < 0 { - continue - } - s.Actions += 1 << v - } -} - -func (s *SSubscription) getResources() []string { - vs := bitmap.Uint64ToIntArray(s.Resources) - resources := make([]string, 0, len(vs)) - for _, v := range vs { - resources = append(resources, converter.resource(v)) - } - return resources -} - -func (s *SSubscription) getActions() []notify.SAction { - vs := bitmap.Uint2IntArray(s.Actions) - actions := make([]notify.SAction, 0, len(vs)) - for _, v := range vs { - actions = append(actions, converter.action(v)) - } - return actions -} - -func (sm *SSubscriptionManager) SubsciptionByEvent(eventStr string, advanceDays int) ([]SSubscription, error) { - event, err := parseEvent(eventStr) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse event %q", event) - } - resourceV := converter.resourceValue(event.ResourceType()) - actionV := converter.actionValue(event.Action()) - q := sm.Query().Equals("advance_days", advanceDays) - q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("resources"), 1<= len(rc.resourceList) { - return "" - } - return rc.resourceList[resourceValue] -} - -func (rc *sConverter) actionValue(action notify.SAction) int { - v, ok := rc.actionValueMap[action] - if !ok { - return -1 - } - return v -} - -func (rc *sConverter) action(actionValue int) notify.SAction { - if actionValue < 0 || actionValue >= len(rc.actionList) { - return notify.SAction("") - } - return rc.actionList[actionValue] -} diff --git a/pkg/notify/models/subscription_receiver.go b/pkg/notify/models/subscription_receiver.go deleted file mode 100644 index 1fd1ef071c..0000000000 --- a/pkg/notify/models/subscription_receiver.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2019 Yunion -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package models - -import ( - "context" - "fmt" - - "golang.org/x/sync/errgroup" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - "yunion.io/x/pkg/errors" - - "yunion.io/x/onecloud/pkg/apis/notify" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules" -) - -var SubscriptionReceiverManager *SSubscriptionReceiverManager - -func init() { - SubscriptionReceiverManager = &SSubscriptionReceiverManager{ - SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( - SSubscriptionReceiver{}, - "subscriptionreceiver_tbl", - "subscriptionreceiver", - "subscriptionreceivers", - ), - } - SubscriptionReceiverManager.SetVirtualObject(ReceiverNotificationManager) -} - -type SSubscriptionReceiverManager struct { - db.SStandaloneResourceBaseManager -} - -type SSubscriptionReceiver struct { - db.SStandaloneResourceBase - - SubscriptionID string `width:"128" charset:"ascii" nullable:"fase" index:"true"` - // role id or receiver id or other and the value type is determined by the ReceiverType - Receiver string `width:"128" charset:"ascii" nullable:"false"` - ReceiverType string `width:"16" charset:"ascii" nullable:"false" index:"true"` - RoleScope string `width:"8" charset:"ascii" nullable:"false"` -} - -const ( - ReceiverRole = "role" - ReceiverNormal = "normal" - ReceiverFeishuRobot = notify.FEISHU_ROBOT - ReceiverDingtalkRobot = notify.DINGTALK_ROBOT - ReceiverWorkwxRobot = notify.WORKWX_ROBOT - ReceiverWeebhook = "webhook" - - ScopeSystem = "system" - ScopeDomain = "domain" - ScopeProject = "project" -) - -func (srm *SSubscriptionReceiverManager) robot(ssid string) (string, error) { - return srm.findSingleReceiver(ReceiverFeishuRobot, ReceiverDingtalkRobot, ReceiverWorkwxRobot) -} - -func (srm *SSubscriptionReceiverManager) webhook(ssid string) (string, error) { - return srm.findSingleReceiver(ssid, ReceiverWeebhook) -} - -func (srm *SSubscriptionReceiverManager) findSingleReceiver(ssid string, receiverTypes ...string) (string, error) { - srs, err := srm.findReceivers(ssid, receiverTypes...) - if err != nil { - return "", err - } - if len(srs) > 1 { - return "", errors.Error("multi receiver") - } - if len(srs) == 0 { - return "", errors.ErrNotFound - } - return srs[0].ReceiverType, nil -} - -func (srm *SSubscriptionReceiverManager) findReceivers(ssid string, receiverTypes ...string) ([]SSubscriptionReceiver, error) { - if len(receiverTypes) == 0 { - return nil, nil - } - q := srm.Query().Equals("subscription_id", ssid) - if len(receiverTypes) == 1 { - q = q.Equals("receiver_type", receiverTypes[0]) - } else { - q = q.In("receiver_type", receiverTypes) - } - srs := make([]SSubscriptionReceiver, 0) - err := db.FetchModelObjects(srm, q, &srs) - if err != nil { - return nil, errors.Wrap(err, "unable to FetchModelObjects") - } - return srs, nil -} - -// TODO: Use cache to increase speed -func (srm *SSubscriptionReceiverManager) getReceivers(ctx context.Context, ssid string, projectDomainId string, projectId string) ([]string, error) { - srs, err := srm.findReceivers(ssid, ReceiverRole, ReceiverNormal) - if err != nil { - return nil, err - } - receivers := make([]string, 0, len(srs)) - roleMap := make(map[string][]string, 3) - receivermap := make(map[string]*[]string, 3) - for _, sr := range srs { - if sr.ReceiverType == ReceiverNormal { - receivers = append(receivers, sr.Receiver) - } else if sr.ReceiverType == ReceiverRole { - roleMap[sr.RoleScope] = append(roleMap[sr.RoleScope], sr.Receiver) - receivermap[sr.RoleScope] = &[]string{} - } - } - errgo, _ := errgroup.WithContext(ctx) - for scope, roles := range roleMap { - receivers := receivermap[scope] - errgo.Go(func() error { - query := jsonutils.NewDict() - query.Set("roles", jsonutils.NewStringArray(roles)) - query.Set("effective", jsonutils.JSONTrue) - switch scope { - case ScopeSystem: - case ScopeDomain: - if len(projectDomainId) == 0 { - return fmt.Errorf("need projectDomainId") - } - query.Set("project_domain_id", jsonutils.NewString(projectDomainId)) - case ScopeProject: - if len(projectId) == 0 { - return fmt.Errorf("need projectId") - } - query.Add(jsonutils.NewString(projectId), "scope", "project", "id") - } - s := auth.GetAdminSession(ctx, "", "") - log.Debugf("query for role-assignments: %s", query.String()) - listRet, err := modules.RoleAssignments.List(s, query) - if err != nil { - return errors.Wrap(err, "unable to list RoleAssignments") - } - log.Debugf("return value for role-assignments: %s", jsonutils.Marshal(listRet)) - for i := range listRet.Data { - ras := listRet.Data[i] - user, err := ras.Get("user") - if err == nil { - id, err := user.GetString("id") - if err != nil { - return errors.Wrap(err, "unable to get user.id from result of RoleAssignments.List") - } - *receivers = append(*receivers, id) - } - } - return nil - }) - } - err = errgo.Wait() - if err != nil { - return nil, err - } - for _, res := range receivermap { - receivers = append(receivers, *res...) - } - return receivers, nil -} - -func (srm *SSubscriptionReceiverManager) create(ctx context.Context, ssid, receiver, receiverType, roleScope string) (*SSubscriptionReceiver, error) { - sr := &SSubscriptionReceiver{ - SubscriptionID: ssid, - Receiver: receiver, - ReceiverType: receiverType, - RoleScope: roleScope, - } - err := srm.TableSpec().Insert(ctx, sr) - if err != nil { - return nil, errors.Wrap(err, "unable to insert") - } - return sr, nil -} - -func (sr *SSubscriptionReceiver) receivingRole() notify.ReceivingRole { - return notify.ReceivingRole{ - Role: sr.Receiver, - Scope: sr.RoleScope, - } -} diff --git a/pkg/notify/models/topic.go b/pkg/notify/models/topic.go new file mode 100644 index 0000000000..5610e86efa --- /dev/null +++ b/pkg/notify/models/topic.go @@ -0,0 +1,403 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "fmt" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/sets" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/bitmap" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +func parseEvent(es string) (notify.SEvent, error) { + ess := strings.Split(es, notify.DelimiterInEvent) + if len(ess) != 2 { + return notify.SEvent{}, fmt.Errorf("invalid event string %q", es) + } + return notify.Event.WithResourceType(ess[0]).WithAction(notify.SAction(ess[1])), nil +} + +type STopicManager struct { + db.SStandaloneResourceBaseManager + db.SEnabledResourceBaseManager +} + +var TopicManager *STopicManager + +func init() { + TopicManager = &STopicManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + STopic{}, + "topic_tbl", + "topic", + "topics", + ), + } + TopicManager.SetVirtualObject(TopicManager) +} + +type STopic struct { + db.SStandaloneResourceBase + db.SEnabledResourceBase + + Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"` + Resources uint64 `nullable:"false"` + Actions uint32 `nullable:"false"` + AdvanceDays int `nullable:"false"` +} + +const ( + DefaultResourceCreateDelete = "resource create or delete" + DefaultResourceChangeConfig = "resource change config" + DefaultResourceUpdate = "resource update" + DefaultResourceReleaseDue1Day = "resource release due 1 day" + DefaultResourceReleaseDue3Day = "resource release due 3 day" + DefaultScheduledTaskExecute = "scheduled task execute" + DefaultScalingPolicyExecute = "scaling policy execute" + DefaultSnapshotPolicyExecute = "snapshot policy execute" +) + +func (sm *STopicManager) InitializeData() error { + initSNames := sets.NewString( + DefaultResourceCreateDelete, + DefaultResourceChangeConfig, + DefaultResourceUpdate, + DefaultResourceReleaseDue1Day, + DefaultResourceReleaseDue3Day, + DefaultScheduledTaskExecute, + DefaultScalingPolicyExecute, + DefaultSnapshotPolicyExecute, + ) + q := sm.Query() + topics := make([]STopic, 0, initSNames.Len()) + err := db.FetchModelObjects(sm, q, &topics) + if err != nil { + return errors.Wrap(err, "unable to FetchModelObjects") + } + for i := range topics { + t := &topics[i] + initSNames.Delete(t.Name) + } + ctx := context.Background() + for _, name := range initSNames.UnsortedList() { + t := new(STopic) + t.Name = name + t.Enabled = tristate.True + switch name { + case DefaultResourceCreateDelete: + t.addResources( + notify.TOPIC_RESOURCE_SERVER, + notify.TOPIC_RESOURCE_SCALINGGROUP, + notify.TOPIC_RESOURCE_IMAGE, + notify.TOPIC_RESOURCE_DISK, + notify.TOPIC_RESOURCE_SNAPSHOT, + notify.TOPIC_RESOURCE_INSTANCESNAPSHOT, + notify.TOPIC_RESOURCE_SNAPSHOTPOLICY, + notify.TOPIC_RESOURCE_NETWORK, + notify.TOPIC_RESOURCE_EIP, + notify.TOPIC_RESOURCE_LOADBALANCER, + notify.TOPIC_RESOURCE_LOADBALANCERACL, + notify.TOPIC_RESOURCE_LOADBALANCERCERTIFICATE, + notify.TOPIC_RESOURCE_BUCKET, + notify.TOPIC_RESOURCE_DBINSTANCE, + notify.TOPIC_RESOURCE_ELASTICCACHE, + ) + t.addAction( + notify.ActionCreate, + notify.ActionDelete, + notify.ActionPendingDelete, + ) + t.Type = notify.TOPIC_TYPE_RESOURCE + case DefaultResourceChangeConfig: + t.addResources( + notify.TOPIC_RESOURCE_SERVER, + notify.TOPIC_RESOURCE_DISK, + notify.TOPIC_RESOURCE_DBINSTANCE, + notify.TOPIC_RESOURCE_ELASTICCACHE, + ) + t.addAction(notify.ActionChangeConfig) + t.Type = notify.TOPIC_TYPE_RESOURCE + case DefaultResourceUpdate: + t.addResources( + notify.TOPIC_RESOURCE_SERVER, + notify.TOPIC_RESOURCE_DISK, + notify.TOPIC_RESOURCE_DBINSTANCE, + notify.TOPIC_RESOURCE_ELASTICCACHE, + ) + t.addAction(notify.ActionUpdate) + t.Type = notify.TOPIC_TYPE_RESOURCE + case DefaultResourceReleaseDue1Day: + t.addResources( + notify.TOPIC_RESOURCE_SERVER, + notify.TOPIC_RESOURCE_DISK, + notify.TOPIC_RESOURCE_EIP, + notify.TOPIC_RESOURCE_LOADBALANCER, + notify.TOPIC_RESOURCE_DBINSTANCE, + notify.TOPIC_RESOURCE_ELASTICCACHE, + ) + t.addAction(notify.ActionExpiredRelease) + t.Type = notify.TOPIC_TYPE_RESOURCE + t.AdvanceDays = 1 + case DefaultResourceReleaseDue3Day: + t.addResources( + notify.TOPIC_RESOURCE_SERVER, + notify.TOPIC_RESOURCE_DISK, + notify.TOPIC_RESOURCE_EIP, + notify.TOPIC_RESOURCE_LOADBALANCER, + notify.TOPIC_RESOURCE_DBINSTANCE, + notify.TOPIC_RESOURCE_ELASTICCACHE, + ) + t.addAction(notify.ActionExpiredRelease) + t.Type = notify.TOPIC_TYPE_RESOURCE + t.AdvanceDays = 3 + case DefaultScheduledTaskExecute: + t.addResources(notify.TOPIC_RESOURCE_SCHEDULEDTASK) + t.addAction(notify.ActionExecute) + t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS + case DefaultScalingPolicyExecute: + t.addResources(notify.TOPIC_RESOURCE_SCALINGPOLICY) + t.addAction(notify.ActionExecute) + t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS + case DefaultSnapshotPolicyExecute: + t.addResources(notify.TOPIC_RESOURCE_SNAPSHOTPOLICY) + t.addAction(notify.ActionExecute) + t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS + } + err := sm.TableSpec().Insert(ctx, t) + if err != nil { + return errors.Wrapf(err, "unable to insert %s", name) + } + } + return nil +} + +func (sm *STopicManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input notify.TopicListInput) (*sqlchemy.SQuery, error) { + return sm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput) +} + +func (sm *STopicManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []notify.TopicDetails { + sRows := sm.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + rows := make([]notify.TopicDetails, len(objs)) + for i := range rows { + rows[i].StandaloneResourceDetails = sRows[i] + ss := objs[i].(*STopic) + rows[i].Resources = ss.getResources() + } + return rows +} + +type SSubscriberDis struct { + SSubscriber + ReceiverName string `json:"receiver_name"` + RoleName string `json:"role_name"` +} + +func (s *STopic) subscriptionReceiverDiss() ([]SSubscriberDis, error) { + q := SubscriberManager.Query().Equals("subscription_id", s.Id) + rq := ReceiverManager.Query("id", "name").SubQuery() + roq := db.RoleCacheManager.Query("id", "name").SubQuery() + q = q.LeftJoin(rq, sqlchemy.Equals(q.Field("receiver"), rq.Field("id"))) + q = q.LeftJoin(roq, sqlchemy.Equals(q.Field("receiver"), roq.Field("id"))) + // It looks strange, but the order of append cannot be changed + q.AppendField(q.QueryFields()...) + q.AppendField(rq.Field("name", "receiver_name")) + q.AppendField(roq.Field("name", "role_name")) + srs := make([]SSubscriberDis, 0) + err := q.All(&srs) + if err != nil { + return nil, errors.Wrap(err, "unable to fetch All") + } + return srs, nil +} + +func (sm *STopicManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewForbiddenError("prohibit creation") +} + +func (ss *STopic) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return input, httperrors.NewForbiddenError("update prohibited") +} + +func (ss *STopic) ValidateDeleteCondition(ctx context.Context) error { + return httperrors.NewForbiddenError("prohibit deletion") +} + +func (s *STopic) addResources(resources ...string) { + for _, resource := range resources { + v := converter.resourceValue(resource) + if v < 0 { + continue + } + s.Resources += 1 << v + } +} + +func (s *STopic) addAction(actions ...notify.SAction) { + for _, action := range actions { + v := converter.actionValue(action) + if v < 0 { + continue + } + s.Actions += 1 << v + } +} + +func (s *STopic) getResources() []string { + vs := bitmap.Uint64ToIntArray(s.Resources) + resources := make([]string, 0, len(vs)) + for _, v := range vs { + resources = append(resources, converter.resource(v)) + } + return resources +} + +func (s *STopic) getActions() []notify.SAction { + vs := bitmap.Uint2IntArray(s.Actions) + actions := make([]notify.SAction, 0, len(vs)) + for _, v := range vs { + actions = append(actions, converter.action(v)) + } + return actions +} + +func (sm *STopicManager) TopicsByEvent(eventStr string, advanceDays int) ([]STopic, error) { + event, err := parseEvent(eventStr) + if err != nil { + return nil, errors.Wrapf(err, "unable to parse event %q", event) + } + resourceV := converter.resourceValue(event.ResourceType()) + actionV := converter.actionValue(event.Action()) + q := sm.Query().Equals("advance_days", advanceDays) + q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("resources"), 1<= len(rc.resourceList) { + return "" + } + return rc.resourceList[resourceValue] +} + +func (rc *sConverter) actionValue(action notify.SAction) int { + v, ok := rc.actionValueMap[action] + if !ok { + return -1 + } + return v +} + +func (rc *sConverter) action(actionValue int) notify.SAction { + if actionValue < 0 || actionValue >= len(rc.actionList) { + return notify.SAction("") + } + return rc.actionList[actionValue] +} diff --git a/pkg/notify/service/handlers.go b/pkg/notify/service/handlers.go index 89d012ed45..048b0e4cb0 100644 --- a/pkg/notify/service/handlers.go +++ b/pkg/notify/service/handlers.go @@ -39,7 +39,6 @@ func InitHandlers(app *appsrv.Application) { models.SubContactManager, db.SharedResourceManager, models.VerificationManager, - models.SubscriptionReceiverManager, } { db.RegisterModelManager(manager) } @@ -51,8 +50,9 @@ func InitHandlers(app *appsrv.Application) { models.NotificationManager, models.ConfigManager, models.TemplateManager, - models.SubscriptionManager, + models.TopicManager, models.RobotManager, + models.SubscriberManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) From 066efeea6594bfaf58c78be8b77aaf052b6b4612 Mon Sep 17 00:00:00 2001 From: rainzm Date: Fri, 19 Mar 2021 16:12:15 +0800 Subject: [PATCH 3/7] feat(notify): adjust subscribers when deleting bots and recipients --- pkg/notify/models/receiver.go | 19 +++++++++++++++++++ pkg/notify/models/robot.go | 20 ++++++++++++++++++++ pkg/notify/models/subscriber.go | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pkg/notify/models/receiver.go b/pkg/notify/models/receiver.go index 48dd796b5d..16f9d52274 100644 --- a/pkg/notify/models/receiver.go +++ b/pkg/notify/models/receiver.go @@ -915,9 +915,28 @@ func (r *SReceiver) Delete(ctx context.Context, userCred mcclient.TokenCredentia return err } } + r.deleteReceiverInSubscriber(ctx) return r.SStatusStandaloneResourceBase.Delete(ctx, userCred) } +func (r *SReceiver) deleteReceiverInSubscriber(ctx context.Context) { + q := SubscriberReceiverManager.Query().Equals("receiver_id", r.Id) + srs := make([]SSubscriberReceiver, 0, 2) + err := db.FetchModelObjects(SubscriberReceiverManager, q, &srs) + if err != nil { + log.Infof("unable to get SubscriberReceiver: %s", err.Error()) + } + for i := range srs { + sr := &srs[i] + _, err := db.Update(sr, func() error { + return sr.MarkDelete() + }) + if err != nil { + log.Errorf("unable to delete subscriber receiver for receiver %q", r.Id) + } + } +} + func (r *SReceiver) IsOwner(userCred mcclient.TokenCredential) bool { return r.Id == userCred.GetUserId() } diff --git a/pkg/notify/models/robot.go b/pkg/notify/models/robot.go index 9049593c23..e0d855fb0e 100644 --- a/pkg/notify/models/robot.go +++ b/pkg/notify/models/robot.go @@ -13,6 +13,7 @@ import ( "yunion.io/x/onecloud/pkg/apis" idenapi "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/apis/notify" api "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/httperrors" @@ -280,3 +281,22 @@ func (r *SRobot) PerformDisable(ctx context.Context, userCred mcclient.TokenCred } return nil, nil } + +func (r *SRobot) PostDelete(ctx context.Context, userCred mcclient.TokenCredential) { + q := SubscriberManager.Query().Equals("type", notify.SUBSCRIBER_TYPE_ROBOT).Equals("identification", r.GetId()) + subscribers := make([]SSubscriber, 0, 2) + err := db.FetchModelObjects(SubscriberManager, q, &subscribers) + if err != nil { + log.Errorf("unable to fetch subscribers with robot %q", r.GetId()) + return + } + for i := range subscribers { + subscriber := &subscribers[i] + _, err := db.Update(r, func() error { + return subscriber.MarkDelete() + }) + if err != nil { + log.Errorf("unable to delete subscriber %q because of deleting of robot %q", subscribers[i].GetId(), r.GetId()) + } + } +} diff --git a/pkg/notify/models/subscriber.go b/pkg/notify/models/subscriber.go index 4ffc7a5151..95c6e7fbfc 100644 --- a/pkg/notify/models/subscriber.go +++ b/pkg/notify/models/subscriber.go @@ -62,7 +62,7 @@ type SSubscriber struct { TopicID string `width:"128" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"` Type string `width:"16" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"` - Identification string `width:"128" charset:"ascii" nullable:"false"` + Identification string `width:"128" charset:"ascii" nullable:"false" index:"true"` RoleScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"` ResourceScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"` } From 76305894654e4de0ac383b0227a4ed41fa69ce94 Mon Sep 17 00:00:00 2001 From: rainzm Date: Fri, 19 Mar 2021 16:36:27 +0800 Subject: [PATCH 4/7] feat(notify): add action 'change_ipaddr' for notify topic --- pkg/apis/notify/event.go | 1 + pkg/notify/models/event_template.go | 1 + pkg/notify/models/topic.go | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/pkg/apis/notify/event.go b/pkg/apis/notify/event.go index b45dc29645..8e33598ed7 100644 --- a/pkg/apis/notify/event.go +++ b/pkg/apis/notify/event.go @@ -32,6 +32,7 @@ var ( ActionResize SAction = "resize" ActionExpiredRelease SAction = "expired_release" ActionExecute SAction = "execute" + ActionChangeIpaddr SAction = "change_ipaddr" ) const ( diff --git a/pkg/notify/models/event_template.go b/pkg/notify/models/event_template.go index 7c95d9577c..16bf7e27e9 100644 --- a/pkg/notify/models/event_template.go +++ b/pkg/notify/models/event_template.go @@ -151,6 +151,7 @@ var action2Topic = make(map[string]string, 0) func init() { action2Topic[string(api.ActionRebuildRoot)] = string(api.ActionUpdate) action2Topic[string(api.ActionResetPassword)] = string(api.ActionUpdate) + action2Topic[string(api.ActionChangeIpaddr)] = string(api.ActionUpdate) } func (lt *SLocalTemplateManager) fillWithTemplate(ctx context.Context, titleOrContent string, contactType string, lang string, event api.SEvent, dis jsonutils.JSONObject) (string, error) { diff --git a/pkg/notify/models/topic.go b/pkg/notify/models/topic.go index 5610e86efa..1956c38442 100644 --- a/pkg/notify/models/topic.go +++ b/pkg/notify/models/topic.go @@ -149,6 +149,9 @@ func (sm *STopicManager) InitializeData() error { notify.TOPIC_RESOURCE_ELASTICCACHE, ) t.addAction(notify.ActionUpdate) + t.addAction(notify.ActionRebuildRoot) + t.addAction(notify.ActionResetPassword) + t.addAction(notify.ActionChangeIpaddr) t.Type = notify.TOPIC_TYPE_RESOURCE case DefaultResourceReleaseDue1Day: t.addResources( @@ -340,6 +343,7 @@ func init() { notify.ActionChangeConfig, notify.ActionExpiredRelease, notify.ActionExecute, + notify.ActionChangeIpaddr, ) } From 311d0efb8060bb1aeecc091d0837a26749345db8 Mon Sep 17 00:00:00 2001 From: rainzm Date: Thu, 17 Jun 2021 16:34:28 +0800 Subject: [PATCH 5/7] feat(notify,region): improvement of news subscription --- .../local-templates/content@cn/COMMON.tmpl | 5 +- .../content@cn/DELETE_WITH_IP.tmpl | 22 ++ .../local-templates/content@en/COMMON.tmpl | 5 +- .../content@en/DELETE_WITH_IP.tmpl | 22 ++ .../local-templates/title@cn/COMMON.tmpl | 2 +- .../local-templates/title@en/COMMON.tmpl | 2 +- cmd/climc/shell/notifyv2/receiver.go | 1 + cmd/climc/shell/notifyv2/robot.go | 16 +- cmd/climc/shell/notifyv2/subscription.go | 2 +- pkg/apis/notify/const.go | 1 + pkg/apis/notify/event.go | 42 +++- pkg/apis/notify/notification.go | 17 +- pkg/apis/notify/receiver.go | 4 + pkg/apis/notify/robot.go | 56 ++--- pkg/apis/notify/subscriber.go | 37 ++- pkg/apis/notify/topic.go | 6 + pkg/appsrv/dispatcher/dispatcher.go | 1 + pkg/cloudcommon/db/db_dispatcher.go | 3 + pkg/cloudcommon/notifyclient/events.go | 5 + pkg/cloudcommon/notifyclient/notify.go | 19 +- .../notifyclient/notify_internal.go | 4 +- pkg/cloudcommon/notifyclient/template.go | 36 +-- pkg/compute/models/billingresource.go | 84 +++++++ pkg/compute/models/guest_actions.go | 5 + pkg/compute/models/guests.go | 1 + pkg/compute/service/handlers.go | 1 + pkg/compute/service/service.go | 2 + pkg/compute/tasks/bucket_create_task.go | 5 + pkg/compute/tasks/bucket_delete_task.go | 5 + .../dbinstance_account_reset_password_task.go | 14 ++ pkg/compute/tasks/dbinstance_change_config.go | 10 +- pkg/compute/tasks/dbinstance_create_task.go | 11 +- pkg/compute/tasks/dbinstance_delete_task.go | 17 +- .../tasks/dbinstance_sync_status_task.go | 6 + pkg/compute/tasks/eip_allocate_task.go | 13 +- pkg/compute/tasks/eip_deallocate_task.go | 5 + ...lasticcache_account_reset_password_task.go | 14 ++ .../tasks/elasticcache_change_spec_task.go | 6 +- pkg/compute/tasks/elasticcache_create_task.go | 12 +- pkg/compute/tasks/elasticcache_delete_task.go | 11 +- .../tasks/elasticcache_syncstatus_task.go | 6 + pkg/compute/tasks/guest_batch_create_task.go | 6 +- pkg/compute/tasks/guest_change_config_task.go | 7 +- pkg/compute/tasks/guest_create_task.go | 29 ++- pkg/compute/tasks/guest_delete_backup_task.go | 6 + pkg/compute/tasks/guest_delete_task.go | 17 +- pkg/compute/tasks/guest_live_migrate_task.go | 5 + pkg/compute/tasks/guest_rebuild_root_task.go | 16 +- pkg/compute/tasks/loadbalancer_create_task.go | 10 +- pkg/compute/tasks/loadbalancer_delete_task.go | 12 +- .../loadbalancer_listener_create_task.go | 6 +- .../loadbalancer_listener_delete_task.go | 6 +- pkg/mcclient/options/notify/receiver.go | 13 + pkg/mcclient/options/notify/subscription.go | 29 ++- pkg/notify/interface.go | 7 +- pkg/notify/models/config.go | 1 - pkg/notify/models/event.go | 68 +++++ pkg/notify/models/event_template.go | 51 +++- pkg/notify/models/notification.go | 146 ++++++++--- pkg/notify/models/receiver.go | 25 ++ pkg/notify/models/receiver_notification.go | 41 +-- pkg/notify/models/robot.go | 13 +- pkg/notify/models/subscriber.go | 163 ++++++++++-- pkg/notify/models/topic.go | 236 +++++++++++++----- pkg/notify/rpc/apis/send_client.go | 24 +- pkg/notify/rpc/apis/send_server.pb.go | 3 +- pkg/notify/rpc/send.go | 2 +- pkg/notify/service/handlers.go | 1 + pkg/notify/tasks/notifications_send_task.go | 8 +- pkg/notify/tasks/repull_subcontact_task.go | 16 +- pkg/notify/tasks/verification_send_task.go | 8 +- 71 files changed, 1188 insertions(+), 323 deletions(-) create mode 100644 build/notify/root/opt/yunion/share/local-templates/content@cn/DELETE_WITH_IP.tmpl create mode 100644 build/notify/root/opt/yunion/share/local-templates/content@en/DELETE_WITH_IP.tmpl create mode 100644 pkg/notify/models/event.go diff --git a/build/notify/root/opt/yunion/share/local-templates/content@cn/COMMON.tmpl b/build/notify/root/opt/yunion/share/local-templates/content@cn/COMMON.tmpl index 7d89030977..a019949601 100644 --- a/build/notify/root/opt/yunion/share/local-templates/content@cn/COMMON.tmpl +++ b/build/notify/root/opt/yunion/share/local-templates/content@cn/COMMON.tmpl @@ -3,4 +3,7 @@ {{- if $d.brand -}} {{ $d.brand }}平台 {{- end -}} -{{ .resource_type_display }}{{ $d.name }}已经成功{{ .action_display }} +{{ .resource_type_display }}{{ $d.name }}已经{{ .action_display }}{{.result_display}} +{{- if eq .result "failed" -}} +,请尽快前往控制台进行处理 +{{- end -}} diff --git a/build/notify/root/opt/yunion/share/local-templates/content@cn/DELETE_WITH_IP.tmpl b/build/notify/root/opt/yunion/share/local-templates/content@cn/DELETE_WITH_IP.tmpl new file mode 100644 index 0000000000..a745916206 --- /dev/null +++ b/build/notify/root/opt/yunion/share/local-templates/content@cn/DELETE_WITH_IP.tmpl @@ -0,0 +1,22 @@ +{{- $d := .resource_details -}} +您在{{ $d.project }}项目的 +{{- if $d.brand -}} +{{ $d.brand }}平台 +{{- end -}} + +{{- if $d.private_dns -}} +,内网地址为{{ $d.private_dns }}:{{ $d.private_connect_port }} +{{- end -}} + +{{- if $d.public_dns -}} +,外网地址为{{ $d.public_dns }}:{{ $d.public_connect_port }} +{{- end -}} + +{{- if $d.ips -}} +,IP地址为{{ $d.ips }} +{{- end -}} + +{{- if $d.ip_addr -}} +,IP地址为{{ $d.ip_addr }} +{{- end -}} +{{ .resource_type_display }}{{ $d.name }}已经删除成功。 diff --git a/build/notify/root/opt/yunion/share/local-templates/content@en/COMMON.tmpl b/build/notify/root/opt/yunion/share/local-templates/content@en/COMMON.tmpl index 4bb31b87ba..9ce0e1d770 100644 --- a/build/notify/root/opt/yunion/share/local-templates/content@en/COMMON.tmpl +++ b/build/notify/root/opt/yunion/share/local-templates/content@en/COMMON.tmpl @@ -1,2 +1,5 @@ {{- $d := .resource_details -}} -Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type_display }} {{ $d.name }} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} has been {{ .action_display }} successfully +Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type_display }} {{ $d.name }} {{ if $d.project -}} in project {{ $d.project }} {{ end -}} has been {{ .action_display }} {{ .result_display }} +{{- if eq .result "failed" -}} +. And please go to the console as soon as possible to process. +{{- end -}} diff --git a/build/notify/root/opt/yunion/share/local-templates/content@en/DELETE_WITH_IP.tmpl b/build/notify/root/opt/yunion/share/local-templates/content@en/DELETE_WITH_IP.tmpl new file mode 100644 index 0000000000..e1a909f968 --- /dev/null +++ b/build/notify/root/opt/yunion/share/local-templates/content@en/DELETE_WITH_IP.tmpl @@ -0,0 +1,22 @@ +{{- $d := .resource_details -}} +Your {{ if $d.brand -}} {{ $d.brand }} {{ end -}} {{ .resource_type_display }} {{ $d.name }} +{{ if $d.project -}} in project {{ $d.project }} {{ end -}} has been deleted successfully +{{- if $d.private_dns -}} +, the intranet address is {{ $d.private_dns }}:{{ $d.private_connect_port }} +{{- end -}} + +{{- if $d.public_dns -}} +, the internet address is {{ $d.public_dns }}:{{ $d.public_connect_port }} +{{- end -}} + +{{- if and $d.address_type $d.address -}} +, the service address is {{ $d.address_type }}{{ $d.address }} +{{- end -}} + +{{- if $d.ips -}} +, the IP address is {{ $d.ips }} +{{- end -}} + +{{- if $d.ip_addr -}} +, the IP address is {{ $d.ip_addr }} +{{- end -}} diff --git a/build/notify/root/opt/yunion/share/local-templates/title@cn/COMMON.tmpl b/build/notify/root/opt/yunion/share/local-templates/title@cn/COMMON.tmpl index 647c8d4910..f89233bb37 100644 --- a/build/notify/root/opt/yunion/share/local-templates/title@cn/COMMON.tmpl +++ b/build/notify/root/opt/yunion/share/local-templates/title@cn/COMMON.tmpl @@ -2,4 +2,4 @@ {{- if $d.project -}} {{ $d.project }}项目的 {{- end -}} -{{ .resource_type_display }}{{ $d.name }}{{ .action_display }}成功 +{{ .resource_type_display }}{{ $d.name }}{{ .action_display }}{{ .result_display }} diff --git a/build/notify/root/opt/yunion/share/local-templates/title@en/COMMON.tmpl b/build/notify/root/opt/yunion/share/local-templates/title@en/COMMON.tmpl index 06201695cd..e2de93a0ef 100644 --- a/build/notify/root/opt/yunion/share/local-templates/title@en/COMMON.tmpl +++ b/build/notify/root/opt/yunion/share/local-templates/title@en/COMMON.tmpl @@ -1,2 +1,2 @@ {{- $d := .resource_details -}} -The {{ .resource_type_display }} {{ .Name }} {{ if $d.project }} in poject {{ $d.project }} {{ end -}} {{ .action_display }} successfully +The {{ .resource_type_display }} {{ .Name }} {{ if $d.project }} in poject {{ $d.project }} {{ end -}} {{ .action_display }} {{ .result_display }} diff --git a/cmd/climc/shell/notifyv2/receiver.go b/cmd/climc/shell/notifyv2/receiver.go index 76bc71a99e..c90134f096 100644 --- a/cmd/climc/shell/notifyv2/receiver.go +++ b/cmd/climc/shell/notifyv2/receiver.go @@ -31,6 +31,7 @@ func init() { cmd.Perform("disable", new(options.ReceiverOptions)) cmd.Perform("trigger-verify", new(options.ReceiverTriggerVerifyOptions)) cmd.Perform("verify", new(options.ReceiverVerifyOptions)) + cmd.Perform("enable-contact-type", new(options.ReceiverEnableContactTypeInput)) cmd.PerformClass("intellij-get", new(options.ReceiverIntellijGetOptions)) cmd.PerformClass("get-types", new(options.ReceiverGetTypeOptions)) } diff --git a/cmd/climc/shell/notifyv2/robot.go b/cmd/climc/shell/notifyv2/robot.go index bbd7d8a63b..6df5f1f100 100644 --- a/cmd/climc/shell/notifyv2/robot.go +++ b/cmd/climc/shell/notifyv2/robot.go @@ -21,12 +21,12 @@ import ( ) func init() { - cmd := shell.NewResourceCmd(&modules.NotifyRobot).WithKeyword("notify-robot") - cmd.List(new(options.RobotListOptions)) - cmd.Create(new(options.RobotCreateOptions)) - cmd.Update(new(options.RobotUpdateOptions)) - cmd.Show(new(options.RobotOptions)) - cmd.Delete(new(options.RobotOptions)) - cmd.Perform("enable", new(options.RobotOptions)) - cmd.Perform("disable", new(options.RobotOptions)) + cmd := shell.NewResourceCmd(&modules.NotifyRobot).WithKeyword("notify-robot") + cmd.List(new(options.RobotListOptions)) + cmd.Create(new(options.RobotCreateOptions)) + cmd.Update(new(options.RobotUpdateOptions)) + cmd.Show(new(options.RobotOptions)) + cmd.Delete(new(options.RobotOptions)) + cmd.Perform("enable", new(options.RobotOptions)) + cmd.Perform("disable", new(options.RobotOptions)) } diff --git a/cmd/climc/shell/notifyv2/subscription.go b/cmd/climc/shell/notifyv2/subscription.go index 2e96c305f3..90dcc1c351 100644 --- a/cmd/climc/shell/notifyv2/subscription.go +++ b/cmd/climc/shell/notifyv2/subscription.go @@ -29,7 +29,7 @@ func init() { cmd1.Create(new(notify.SubscriberCreateOptions)) cmd1.Show(new(notify.SubscriberOptions)) cmd1.Delete(new(notify.SubscriberOptions)) - cmd1.Perform("set-receiver", new(notify.SubscriberSetReceiverOptions)) + cmd1.Perform("change", new(notify.SubscriberChangeOptions)) cmd1.Perform("enable", new(notify.SubscriberOptions)) cmd1.Perform("disable", new(notify.SubscriberOptions)) } diff --git a/pkg/apis/notify/const.go b/pkg/apis/notify/const.go index 6aa6fd19f0..3c3f3c0a8a 100644 --- a/pkg/apis/notify/const.go +++ b/pkg/apis/notify/const.go @@ -105,6 +105,7 @@ const ( TOPIC_RESOURCE_DBINSTANCE = "dbinstance" TOPIC_RESOURCE_ELASTICCACHE = "elasticcache" TOPIC_RESOURCE_SCHEDULEDTASK = "scheduledtask" + TOPIC_RESOURCE_BAREMETAL = "baremetal" SUBSCRIBER_TYPE_ROLE = "role" SUBSCRIBER_TYPE_ROBOT = "robot" diff --git a/pkg/apis/notify/event.go b/pkg/apis/notify/event.go index 8e33598ed7..a3a6b30b2c 100644 --- a/pkg/apis/notify/event.go +++ b/pkg/apis/notify/event.go @@ -33,6 +33,15 @@ var ( ActionExpiredRelease SAction = "expired_release" ActionExecute SAction = "execute" ActionChangeIpaddr SAction = "change_ipaddr" + ActionSyncStatus SAction = "sync_status" + ActionCleanData SAction = "clean_data" + ActionMigrate SAction = "migrate" + + ActionCreateBackupServer SAction = "add_backup_server" + ActionDelBackupServer SAction = "delete_backup_server" + + ResultFailed SResult = "failed" + ResultSucceed SResult = "succeed" ) const ( @@ -41,9 +50,12 @@ const ( type SAction string +type SResult string + type SEvent struct { resourceType string action SAction + result SResult } func (se SEvent) WithResourceType(rt string) SEvent { @@ -56,6 +68,11 @@ func (se SEvent) WithAction(a SAction) SEvent { return se } +func (se SEvent) WithResult(r SResult) SEvent { + se.result = r + return se +} + func (se SEvent) ResourceType() string { return se.resourceType } @@ -64,6 +81,29 @@ func (se SEvent) Action() SAction { return se.action } +func (se SEvent) ActionWithResult(delimiter string) string { + ar := string(se.action) + if len(se.result) > 0 { + ar += delimiter + string(se.result) + } + return strings.ToUpper(ar) +} + +func (se SEvent) Result() SResult { + if se.result == "" { + return ResultSucceed + } + return se.result +} + func (se SEvent) String() string { - return strings.ToUpper(fmt.Sprintf("%s%s%s", se.ResourceType(), DelimiterInEvent, se.Action())) + return se.StringWithDeli(DelimiterInEvent) +} + +func (se SEvent) StringWithDeli(delimiter string) string { + str := strings.ToUpper(fmt.Sprintf("%s%s%s", se.ResourceType(), delimiter, se.Action())) + if se.result != "" { + str += delimiter + strings.ToUpper(string(se.result)) + } + return str } diff --git a/pkg/apis/notify/notification.go b/pkg/apis/notify/notification.go index 0abe056c1e..8d1c4c7a0d 100644 --- a/pkg/apis/notify/notification.go +++ b/pkg/apis/notify/notification.go @@ -32,9 +32,9 @@ type NotificationCreateInput struct { // description: direct contact, admin privileges required // required: false Contacts []string `json:"contacts"` - // description: robots - // example: feishu robot - Robots []string `json:"robots"` + // description: robots + // example: feishu robot + Robots []string `json:"robots"` // description: contact type // required: ture // example: email @@ -87,14 +87,19 @@ type NotificationListInput struct { Tag string } +type SContact struct { + Contact string `json:"contact"` + ContactType string `json:"contact_type"` +} + type NotificationManagerEventNotifyInput struct { // description: ids or names of receiver // required: false - // example: {"adfb720ccdd34c638346ea4fa7a713a8", "zhangsan"} - Receivers []string `json:"receivers"` + // example: {"adfb720ccdd34c638346ea4fa7a713a8"} + ReceiverIds []string `json:"receiver_ids"` // description: direct contact, admin privileges required // required: false - Contacts []string `json:"contacts"` + Contacts []SContact `json:"contacts"` // description: contact types // required: false // example: email diff --git a/pkg/apis/notify/receiver.go b/pkg/apis/notify/receiver.go index d28035f0fd..b120b51343 100644 --- a/pkg/apis/notify/receiver.go +++ b/pkg/apis/notify/receiver.go @@ -162,3 +162,7 @@ type ReceiverIntellijGetInput struct { // required: true Scope string `json:"scope"` } + +type ReceiverEnableContactTypeInput struct { + EnabledContactTypes []string `json:"enabled_contact_types"` +} diff --git a/pkg/apis/notify/robot.go b/pkg/apis/notify/robot.go index 2c989065f0..7a0cd27383 100644 --- a/pkg/apis/notify/robot.go +++ b/pkg/apis/notify/robot.go @@ -3,41 +3,41 @@ package notify import "yunion.io/x/onecloud/pkg/apis" type RobotCreateInput struct { - apis.SharableVirtualResourceCreateInput - // description: robot type - // enum: feishu,dingtalk,workwx,webhook - // example: webhook - Type string `json:"type"` - // description: address - // example: http://helloworld.io/test/webhook - Address string `json:"address"` - // description: Language preference - // example: zh_CN - Lang string `json:"lang"` + apis.SharableVirtualResourceCreateInput + // description: robot type + // enum: feishu,dingtalk,workwx,webhook + // example: webhook + Type string `json:"type"` + // description: address + // example: http://helloworld.io/test/webhook + Address string `json:"address"` + // description: Language preference + // example: zh_CN + Lang string `json:"lang"` } type RobotDetails struct { - apis.SharableVirtualResourceDetails + apis.SharableVirtualResourceDetails } type RobotListInput struct { - apis.SharableVirtualResourceListInput - apis.EnabledResourceBaseListInput - // description: robot type - // enum: feishu,dingtalk,workwx,webhook - // example: webhook - Type string `json:"type"` - // description: Language preference - // example: en - Lang string `json:"lang"` + apis.SharableVirtualResourceListInput + apis.EnabledResourceBaseListInput + // description: robot type + // enum: feishu,dingtalk,workwx,webhook + // example: webhook + Type string `json:"type"` + // description: Language preference + // example: en + Lang string `json:"lang"` } type RobotUpdateInput struct { - apis.SharableVirtualResourceBaseUpdateInput - // description: address - // example: http://helloworld.io/test/webhook - Address string `json:"address"` - // description: Language preference - // example: en - Lang string `json:"lang"` + apis.SharableVirtualResourceBaseUpdateInput + // description: address + // example: http://helloworld.io/test/webhook + Address string `json:"address"` + // description: Language preference + // example: en + Lang string `json:"lang"` } diff --git a/pkg/apis/notify/subscriber.go b/pkg/apis/notify/subscriber.go index bc3cf11834..4632079a85 100644 --- a/pkg/apis/notify/subscriber.go +++ b/pkg/apis/notify/subscriber.go @@ -17,7 +17,7 @@ package notify import "yunion.io/x/onecloud/pkg/apis" type SubscriberCreateInput struct { - apis.VirtualResourceCreateInput + apis.StandaloneAnonResourceCreateInput // description: Id of Topic // required @@ -27,6 +27,14 @@ type SubscriberCreateInput struct { // enum: system,domain,project ResourceScope string + // description: project id or domain id of resource + // example: 1e3824756bac4ac084e784ed297ec652 + ResourceAttributionId string + + // description: domain id of resource + // example: 1e3824756bac4ac084e784ed297ec652 + DomainId string + // description: Type of subscriber // enum: receiver,robot,role Type string @@ -43,10 +51,29 @@ type SubscriberCreateInput struct { // description: Robot(Id or Name) which is required when the type is 'robot' will Subscribe TopicID Robot string + + // description: scope + // enum: system,domain + Scope string +} + +type SubscriberChangeInput struct { + // description: receivers which is required when the type is 'receiver' will Subscribe TopicID + Receivers []string + + // description: Role(Id or Name) which is required when the type is 'role' will Subscribe TopicID + Role string + + // description: The scope of role subscribers + // enum: system,domain,project + RoleScope string + + // description: Robot(Id or Name) which is required when the type is 'robot' will Subscribe TopicID + Robot string } type SubscriberListInput struct { - apis.VirtualResourceListInput + apis.StandaloneAnonResourceListInput apis.EnabledResourceBaseListInput // description: topic id @@ -59,6 +86,10 @@ type SubscriberListInput struct { // description: type // enum: receiver,robot,role Type string + + // description: scope + // enum: system,domain + Scope string } type Identification struct { @@ -69,7 +100,7 @@ type Identification struct { } type SubscriberDetails struct { - apis.VirtualResourceDetails + apis.StandaloneAnonResourceDetails SSubscriber // description: receivers diff --git a/pkg/apis/notify/topic.go b/pkg/apis/notify/topic.go index 7fc8f3e398..024ad1c0b0 100644 --- a/pkg/apis/notify/topic.go +++ b/pkg/apis/notify/topic.go @@ -28,3 +28,9 @@ type TopicDetails struct { // example: ["server", "eip", "disk"] Resources []string `json:"resource_types"` } + +type PerformEnableInput struct { +} + +type PerformDisableInput struct { +} diff --git a/pkg/appsrv/dispatcher/dispatcher.go b/pkg/appsrv/dispatcher/dispatcher.go index 0809b6621d..db97d7053f 100644 --- a/pkg/appsrv/dispatcher/dispatcher.go +++ b/pkg/appsrv/dispatcher/dispatcher.go @@ -197,6 +197,7 @@ func mergeQueryParams(params map[string]string, query jsonutils.JSONObject, excl } func listHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + log.Infof("start list handler") manager, params, query, _ := fetchEnv(ctx, w, r) handleList(ctx, w, manager, nil, mergeQueryParams(params, query)) } diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index 531b4f783f..00efae442a 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -1155,6 +1155,9 @@ func FetchModelObjects(modelManager IModelManager, query *sqlchemy.SQuery, targe targetsValue := reflect.Indirect(reflect.ValueOf(targets)) for rows.Next() { + if modelManager.Keyword() == "server" { + log.Infof("one guest") + } m, err := NewModelObject(modelManager) if err != nil { return err diff --git a/pkg/cloudcommon/notifyclient/events.go b/pkg/cloudcommon/notifyclient/events.go index d4e1fca651..4834760213 100644 --- a/pkg/cloudcommon/notifyclient/events.go +++ b/pkg/cloudcommon/notifyclient/events.go @@ -52,6 +52,11 @@ var ( ActionExpiredRelease = api.ActionExpiredRelease ActionExecute = api.ActionExecute + ActionMigrate = api.ActionMigrate + ActionCreateBackupServer = api.ActionCreateBackupServer + ActionDelBackupServer = api.ActionDelBackupServer + ActionSyncStatus = api.ActionSyncStatus + ActionPendingDelete = api.ActionPendingDelete ) diff --git a/pkg/cloudcommon/notifyclient/notify.go b/pkg/cloudcommon/notifyclient/notify.go index 2b975975a1..1a3fce5ceb 100644 --- a/pkg/cloudcommon/notifyclient/notify.go +++ b/pkg/cloudcommon/notifyclient/notify.go @@ -129,7 +129,7 @@ func NotifyAllWithoutRobotWithCtx(ctx context.Context, recipientId []string, isG // NotifyRobot will send messages via all robot contact type such as dingtalk-robot. func NotifyRobot(robotIds []string, priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) error { - return NotifyRobotWithCtx(context.Background(), robotIds, priority, event, data) + return NotifyRobotWithCtx(context.Background(), robotIds, priority, event, data) } // NotifyRobot will send messages via all robot contact type such as dingtalk-robot. @@ -141,7 +141,7 @@ func NotifyRobotWithCtx(ctx context.Context, robotIds []string, priority npk.TNo event: event, data: data, }) - return nil + return nil } func SystemNotify(priority npk.TNotifyPriority, event string, data jsonutils.JSONObject) { @@ -221,7 +221,9 @@ type SEventMessage struct { type SEventNotifyParam struct { Obj db.IModel + ResourceType string Action api.SAction + IsFail bool ObjDetailsDecorator func(context.Context, *jsonutils.JSONDict) AdvanceDays int } @@ -247,9 +249,6 @@ func (t *eventTask) Run() { } func EventNotify(ctx context.Context, userCred mcclient.TokenCredential, ep SEventNotifyParam) { - // disable EventNotify for now - return - ret, err := db.FetchCustomizeColumns(ep.Obj.GetModelManager(), ctx, userCred, jsonutils.NewDict(), []interface{}{ep.Obj}, stringutils2.SSortedStrings{}, false) if err != nil { log.Errorf("unable to FetchCustomizeColumns: %v", err) @@ -263,7 +262,14 @@ func EventNotify(ctx context.Context, userCred mcclient.TokenCredential, ep SEve if ep.ObjDetailsDecorator != nil { ep.ObjDetailsDecorator(ctx, objDetails) } - event := api.Event.WithAction(ep.Action).WithResourceType(ep.Obj.GetModelManager().Keyword()) + rt := ep.ResourceType + if len(rt) == 0 { + rt = ep.Obj.GetModelManager().Keyword() + } + event := api.Event.WithAction(ep.Action).WithResourceType(rt) + if ep.IsFail { + event = event.WithResult(api.ResultFailed) + } var ( projectId string projectDomainId string @@ -274,6 +280,7 @@ func EventNotify(ctx context.Context, userCred mcclient.TokenCredential, ep SEve projectDomainId = ownerId.GetProjectDomainId() } params := api.NotificationManagerEventNotifyInput{ + ReceiverIds: []string{userCred.GetUserId()}, ResourceDetails: objDetails, Event: event.String(), AdvanceDays: ep.AdvanceDays, diff --git a/pkg/cloudcommon/notifyclient/notify_internal.go b/pkg/cloudcommon/notifyclient/notify_internal.go index b83bea5b90..395613b7de 100644 --- a/pkg/cloudcommon/notifyclient/notify_internal.go +++ b/pkg/cloudcommon/notifyclient/notify_internal.go @@ -212,7 +212,7 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, var langMap map[language.Tag]*sTarget if p.channel == npk.NotifyByRobot { - langMap, err = langRobot(ctx, p.robots) + langMap, err = langRobot(ctx, p.robots) } else { langMap, err = lang(ctx, p.channel, reIds, p.contacts) if err != nil { @@ -226,7 +226,7 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage, msg := npk.SNotifyMessage{} msg.Uid = t.reIds msg.Priority = p.priority - msg.Robots = p.robots + msg.Robots = p.robots msg.Contacts = t.contacts msg.ContactType = p.channel topic, _ := getContent(langSuffix, p.event, "title", p.channel, p.data) diff --git a/pkg/cloudcommon/notifyclient/template.go b/pkg/cloudcommon/notifyclient/template.go index 2cd3e2e549..717cb28d8f 100644 --- a/pkg/cloudcommon/notifyclient/template.go +++ b/pkg/cloudcommon/notifyclient/template.go @@ -65,26 +65,26 @@ func getUserLang(uids []string) (map[string]string, error) { } func getRobotLang(robots []string) (map[string]string, error) { - s, err := AdminSessionGenerator(context.Background(), consts.GetRegion(), "") - if err != nil { - return nil, err - } - robotLang := make(map[string]string) - if len(robots) > 0 { - params := jsonutils.NewDict() + s, err := AdminSessionGenerator(context.Background(), consts.GetRegion(), "") + if err != nil { + return nil, err + } + robotLang := make(map[string]string) + if len(robots) > 0 { + params := jsonutils.NewDict() params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(robots, ",")))) params.Set("scope", jsonutils.NewString("system")) - ret, err := modules.NotifyRobot.List(s, params) - if err != nil { - return nil, err - } - for i := range ret.Data { - id, _ := ret.Data[i].GetString("id") - langStr, _ := ret.Data[i].GetString("lang") - robotLang[id] = langStr - } - } - return robotLang, nil + ret, err := modules.NotifyRobot.List(s, params) + if err != nil { + return nil, err + } + for i := range ret.Data { + id, _ := ret.Data[i].GetString("id") + langStr, _ := ret.Data[i].GetString("lang") + robotLang[id] = langStr + } + } + return robotLang, nil } func init() { diff --git a/pkg/compute/models/billingresource.go b/pkg/compute/models/billingresource.go index 9b5bad5494..3326363481 100644 --- a/pkg/compute/models/billingresource.go +++ b/pkg/compute/models/billingresource.go @@ -16,13 +16,17 @@ package models import ( "context" + "reflect" "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/billing" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -204,3 +208,83 @@ func ParseBillingCycleInput(billingBase *SBillingResourceBase, input apis.Postpa return &bc, nil } + +type SBillingResourceCheckManager struct { + db.SResourceBaseManager +} + +type SBillingResourceCheck struct { + db.SResourceBase + ResourceId string `width:"128" charset:"ascii" index:"true"` + ResourceType string `width:"36" charset:"ascii" index:"true"` + AdvanceDays int + LastCheck time.Time +} + +var BillingResourceCheckManager *SBillingResourceCheckManager + +func init() { + BillingResourceCheckManager = &SBillingResourceCheckManager{ + SResourceBaseManager: db.NewResourceBaseManager( + SBillingResourceCheck{}, + "billingresourcecheck_tbl", + "billingresourcecheck", + "billingresourcechecks", + ), + } + BillingResourceCheckManager.SetVirtualObject(BillingResourceCheckManager) +} + +func (bm *SBillingResourceCheckManager) Create(ctx context.Context, resourceId, resourceType string, advanceDays int) error { + bc := SBillingResourceCheck{ + ResourceId: resourceId, + ResourceType: resourceType, + AdvanceDays: advanceDays, + LastCheck: time.Now(), + } + return bm.TableSpec().Insert(ctx, &bc) +} + +var advanceDays []int = []int{1, 3} + +func CheckBillingResourceExpireAt(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + billingResourceManagers := []db.IModelManager{ + GuestManager, + DBInstanceManager, + ElasticcacheManager, + } + for _, advanceDay := range advanceDays { + for _, manager := range billingResourceManagers { + upLimit := time.Now().AddDate(0, 0, advanceDay) + downLimit := time.Now().AddDate(0, 0, advanceDay-1) + v := reflect.MakeSlice(reflect.SliceOf(manager.TableSpec().DataType()), 0, 0) + q := manager.Query().LE("expired_at", upLimit).GE("expired_at", downLimit) + + bq := BillingResourceCheckManager.Query("resource_id").Equals("resource_type", manager.Keyword()).Equals("advance_days", advanceDay).SubQuery() + q = q.LeftJoin(bq, sqlchemy.Equals(q.Field("id"), bq.Field("resource_id"))) + q = q.IsNull("resource_id") + + vp := reflect.New(v.Type()) + vp.Elem().Set(v) + err := db.FetchModelObjects(manager, q, vp.Interface()) + if err != nil { + log.Errorf("unable to list %s: %v", manager.KeywordPlural(), err) + } + + v = vp.Elem() + log.Debugf("%s length of v: %d", manager.Alias(), v.Len()) + for i := 0; i < v.Len(); i++ { + m := v.Index(i).Addr().Interface().(db.IModel) + notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ + Obj: m, + Action: notifyclient.ActionExpiredRelease, + AdvanceDays: advanceDay, + }) + err := BillingResourceCheckManager.Create(ctx, m.GetId(), manager.Keyword(), advanceDay) + if err != nil { + log.Errorf("unable to create billingresourcecheck for resource %s %s", manager.Keyword(), m.GetId()) + } + } + } + } +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 22b24d6301..f5c8993d9b 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -973,8 +973,13 @@ func (self *SGuest) EventNotify(ctx context.Context, userCred mcclient.TokenCred } } } + var resourceType string + if self.Hypervisor == api.HYPERVISOR_BAREMETAL { + resourceType = noapi.TOPIC_RESOURCE_BAREMETAL + } notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ Obj: self, + ResourceType: resourceType, Action: action, ObjDetailsDecorator: detailsDecro, AdvanceDays: 0, diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index f623055522..746ee51995 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -91,6 +91,7 @@ func init() { "servers", ), } + log.Infof("init GuestManager") GuestManager.SetVirtualObject(GuestManager) GuestManager.SetAlias("guest", "guests") } diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index c7594644bb..427b129114 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -95,6 +95,7 @@ func InitHandlers(app *appsrv.Application) { models.CloudimageManager, models.WafRuleStatementManager, + models.BillingResourceCheckManager, } { db.RegisterModelManager(manager) } diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index ab83ade848..a563a3ef9f 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -165,6 +165,8 @@ func StartService() { cron.AddJobEveryFewHour("InspectAllTemplate", 1, 0, 0, models.GuestTemplateManager.InspectAllTemplate, true) cron.AddJobAtIntervalsWithStartRun("ScheduledTaskCheck", time.Duration(60)*time.Second, models.ScheduledTaskManager.Timer, true) + + cron.AddJobEveryFewHour("CheckBillingResourceExpireAt", 1, 0, 0, models.CheckBillingResourceExpireAt, true) go cron.Start2(ctx, electObj) // init auto scaling controller diff --git a/pkg/compute/tasks/bucket_create_task.go b/pkg/compute/tasks/bucket_create_task.go index 38f4c6587e..fd36b901a4 100644 --- a/pkg/compute/tasks/bucket_create_task.go +++ b/pkg/compute/tasks/bucket_create_task.go @@ -22,6 +22,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -53,6 +54,10 @@ func (task *BucketCreateTask) OnInit(ctx context.Context, obj db.IStandaloneMode } bucket.SetStatus(task.UserCred, api.BUCKET_STATUS_READY, "BucketCreateTask") + notifyclient.EventNotify(ctx, task.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: bucket, + Action: notifyclient.ActionCreate, + }) logclient.AddActionLogWithStartable(task, bucket, logclient.ACT_ALLOCATE, nil, task.UserCred, true) task.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/bucket_delete_task.go b/pkg/compute/tasks/bucket_delete_task.go index 81ea7219e2..ff58af20b2 100644 --- a/pkg/compute/tasks/bucket_delete_task.go +++ b/pkg/compute/tasks/bucket_delete_task.go @@ -22,6 +22,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -52,6 +53,10 @@ func (task *BucketDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneMode return } + notifyclient.EventNotify(ctx, task.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: bucket, + Action: notifyclient.ActionDelete, + }) logclient.AddActionLogWithStartable(task, bucket, logclient.ACT_DELETE, nil, task.UserCred, true) task.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/dbinstance_account_reset_password_task.go b/pkg/compute/tasks/dbinstance_account_reset_password_task.go index fa9d4a395a..09a1c4a9d5 100644 --- a/pkg/compute/tasks/dbinstance_account_reset_password_task.go +++ b/pkg/compute/tasks/dbinstance_account_reset_password_task.go @@ -23,6 +23,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -73,7 +74,20 @@ func (self *DBInstanceAccountResetPasswordTask) OnInit(ctx context.Context, obj } } + self.resetPasswdNotify(ctx, instance, account, password) account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, "") logclient.AddActionLogWithStartable(self, account, logclient.ACT_RESET_PASSWORD, nil, self.UserCred, true) self.SetStageComplete(ctx, nil) } + +func (self *DBInstanceAccountResetPasswordTask) resetPasswdNotify(ctx context.Context, rds *models.SDBInstance, account *models.SDBInstanceAccount, password string) { + detailsDecro := func(ctx context.Context, details *jsonutils.JSONDict) { + details.Set("account", jsonutils.NewString(account.GetName())) + details.Set("password", jsonutils.NewString(password)) + } + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: rds, + Action: notifyclient.ActionResetPassword, + ObjDetailsDecorator: detailsDecro, + }) +} diff --git a/pkg/compute/tasks/dbinstance_change_config.go b/pkg/compute/tasks/dbinstance_change_config.go index 3404e08ff8..9cc9d51561 100644 --- a/pkg/compute/tasks/dbinstance_change_config.go +++ b/pkg/compute/tasks/dbinstance_change_config.go @@ -41,6 +41,11 @@ func (self *DBInstanceChangeConfigTask) taskFailed(ctx context.Context, dbinstan dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_CHANGE_CONFIG_FAILED, err.Error()) db.OpsLog.LogEvent(dbinstance, db.ACT_CHANGE_CONFIG, err, self.GetUserCred()) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CHANGE_CONFIG, err, self.UserCred, false) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionChangeConfig, + IsFail: true, + }) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } @@ -64,7 +69,10 @@ func (self *DBInstanceChangeConfigTask) OnInit(ctx context.Context, obj db.IStan func (self *DBInstanceChangeConfigTask) OnDBInstanceChangeConfigComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { dbinstance := obj.(*models.SDBInstance) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CHANGE_CONFIG, nil, self.UserCred, true) - notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionChangeConfig) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionChangeConfig, + }) self.SetStage("OnSyncDBInstanceStatusComplete", nil) models.StartResourceSyncStatusTask(ctx, self.UserCred, dbinstance, "DBInstanceSyncStatusTask", self.GetTaskId()) } diff --git a/pkg/compute/tasks/dbinstance_create_task.go b/pkg/compute/tasks/dbinstance_create_task.go index a0c2ccb477..e987edcd2c 100644 --- a/pkg/compute/tasks/dbinstance_create_task.go +++ b/pkg/compute/tasks/dbinstance_create_task.go @@ -40,6 +40,11 @@ func (self *DBInstanceCreateTask) taskFailed(ctx context.Context, dbinstance *mo dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_CREATE_FAILED, err.Error()) db.OpsLog.LogEvent(dbinstance, db.ACT_CREATE, err, self.GetUserCred()) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CREATE, err, self.UserCred, false) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } @@ -77,7 +82,11 @@ func (self *DBInstanceCreateTask) OnCreateDBInstanceCompleteFailed(ctx context.C func (self *DBInstanceCreateTask) OnSyncDBInstanceStatusComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { dbinstance := obj.(*models.SDBInstance) - notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionCreate) + //notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionCreate) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionCreate, + }) self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/dbinstance_delete_task.go b/pkg/compute/tasks/dbinstance_delete_task.go index f18bb8fe12..50627d8090 100644 --- a/pkg/compute/tasks/dbinstance_delete_task.go +++ b/pkg/compute/tasks/dbinstance_delete_task.go @@ -41,6 +41,11 @@ func (self *DBInstanceDeleteTask) taskFailed(ctx context.Context, dbinstance *mo dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_DELETE_FAILED, err.Error()) db.OpsLog.LogEvent(dbinstance, db.ACT_DELETE, err, self.GetUserCred()) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_DELETE, err, self.UserCred, false) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionDelete, + IsFail: true, + }) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } @@ -83,13 +88,21 @@ func (self *DBInstanceDeleteTask) DeleteDBInstanceComplete(ctx context.Context, self.taskFailed(ctx, dbinstance, errors.Wrap(err, "dbinstance.Purge")) return } - notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionDelete) + //notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionDelete) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionDelete, + }) self.SetStageComplete(ctx, nil) return } self.DeleteBackups(ctx, dbinstance, nil) - notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionDelete) + //notifyclient.NotifyWebhook(ctx, self.UserCred, dbinstance, notifyclient.ActionDelete) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionDelete, + }) } func (self *DBInstanceDeleteTask) DeleteBackups(ctx context.Context, instance *models.SDBInstance, data jsonutils.JSONObject) { diff --git a/pkg/compute/tasks/dbinstance_sync_status_task.go b/pkg/compute/tasks/dbinstance_sync_status_task.go index 683f712051..597bb38bed 100644 --- a/pkg/compute/tasks/dbinstance_sync_status_task.go +++ b/pkg/compute/tasks/dbinstance_sync_status_task.go @@ -23,6 +23,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -39,6 +40,11 @@ func (self *DBInstanceSyncStatusTask) taskFailed(ctx context.Context, dbinstance dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_UNKNOWN, err.Error()) db.OpsLog.LogEvent(dbinstance, db.ACT_SYNC_STATUS, err, self.GetUserCred()) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: dbinstance, + Action: notifyclient.ActionSyncStatus, + IsFail: true, + }) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } diff --git a/pkg/compute/tasks/eip_allocate_task.go b/pkg/compute/tasks/eip_allocate_task.go index 5d9074ac77..35428ee260 100644 --- a/pkg/compute/tasks/eip_allocate_task.go +++ b/pkg/compute/tasks/eip_allocate_task.go @@ -43,6 +43,11 @@ func init() { func (self *EipAllocateTask) onFailed(ctx context.Context, eip *models.SElasticip, reason jsonutils.JSONObject) { eip.SetStatus(self.UserCred, api.EIP_STATUS_ALLOCATE_FAIL, reason.String()) self.setGuestAllocateEipFailed(eip, reason) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: eip, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } @@ -159,6 +164,10 @@ func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel } } else { logclient.AddActionLogWithStartable(self, eip, logclient.ACT_ALLOCATE, nil, self.UserCred, true) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: eip, + Action: notifyclient.ActionCreate, + }) self.SetStageComplete(ctx, nil) } } @@ -166,10 +175,6 @@ func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel func (self *EipAllocateTask) OnEipAssociateComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { eip := obj.(*models.SElasticip) logclient.AddActionLogWithStartable(self, eip, logclient.ACT_ALLOCATE, nil, self.UserCred, true) - notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ - Obj: eip, - Action: notifyclient.ActionCreate, - }) self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/eip_deallocate_task.go b/pkg/compute/tasks/eip_deallocate_task.go index 1c4a6c8d13..7abd7577fc 100644 --- a/pkg/compute/tasks/eip_deallocate_task.go +++ b/pkg/compute/tasks/eip_deallocate_task.go @@ -42,6 +42,11 @@ func (self *EipDeallocateTask) taskFail(ctx context.Context, eip *models.SElasti eip.SetStatus(self.UserCred, api.EIP_STATUS_DEALLOCATE_FAIL, msg.String()) db.OpsLog.LogEvent(eip, db.ACT_DELOCATE, msg, self.GetUserCred()) logclient.AddActionLogWithStartable(self, eip, logclient.ACT_DELETE, msg, self.UserCred, false) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: eip, + Action: notifyclient.ActionDelete, + IsFail: true, + }) self.SetStageFailed(ctx, msg) return } diff --git a/pkg/compute/tasks/elasticcache_account_reset_password_task.go b/pkg/compute/tasks/elasticcache_account_reset_password_task.go index fc5f7aa31f..1f72853b38 100644 --- a/pkg/compute/tasks/elasticcache_account_reset_password_task.go +++ b/pkg/compute/tasks/elasticcache_account_reset_password_task.go @@ -68,7 +68,21 @@ func (self *ElasticcacheAccountResetPasswordTask) OnInit(ctx context.Context, ob ec.(*models.SElasticcache).SetStatus(self.GetUserCred(), api.ELASTIC_CACHE_STATUS_RUNNING, "") logclient.AddActionLogWithStartable(self, ec, logclient.ACT_RESET_PASSWORD, "", self.UserCred, true) } + password, _ := self.GetParams().GetString("password") + self.resetPasswordNotify(ctx, ec.(*models.SElasticcache), ea, password) logclient.AddActionLogWithStartable(self, ea, logclient.ACT_RESET_PASSWORD, "", self.UserCred, true) self.SetStageComplete(ctx, nil) } } + +func (self *ElasticcacheAccountResetPasswordTask) resetPasswordNotify(ctx context.Context, ec *models.SElasticcache, account *models.SElasticcacheAccount, password string) { + detailDecro := func(ctx context.Context, details *jsonutils.JSONDict) { + details.Set("account", jsonutils.NewString(account.GetName())) + details.Set("password", jsonutils.NewString(password)) + } + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: ec, + Action: notifyclient.ActionResetPassword, + ObjDetailsDecorator: detailDecro, + }) +} diff --git a/pkg/compute/tasks/elasticcache_change_spec_task.go b/pkg/compute/tasks/elasticcache_change_spec_task.go index 6aa6d1a46c..5afec1f5da 100644 --- a/pkg/compute/tasks/elasticcache_change_spec_task.go +++ b/pkg/compute/tasks/elasticcache_change_spec_task.go @@ -40,7 +40,11 @@ func (self *ElasticcacheChangeSpecTask) taskFail(ctx context.Context, ec *models ec.SetStatus(self.GetUserCred(), api.ELASTIC_CACHE_STATUS_CHANGE_FAILED, reason.String()) db.OpsLog.LogEvent(ec, db.ACT_CHANGE_FLAVOR, reason, self.UserCred) logclient.AddActionLogWithStartable(self, ec, logclient.ACT_VM_CHANGE_FLAVOR, reason, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, ec.Id, ec.Name, api.ELASTIC_CACHE_STATUS_CHANGE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: ec, + Action: notifyclient.ActionChangeConfig, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } diff --git a/pkg/compute/tasks/elasticcache_create_task.go b/pkg/compute/tasks/elasticcache_create_task.go index 8bb26967a9..7dc1fd489f 100644 --- a/pkg/compute/tasks/elasticcache_create_task.go +++ b/pkg/compute/tasks/elasticcache_create_task.go @@ -40,7 +40,11 @@ func (self *ElasticcacheCreateTask) taskFail(ctx context.Context, elasticcache * elasticcache.SetStatus(self.GetUserCred(), api.ELASTIC_CACHE_STATUS_CREATE_FAILED, reason.String()) db.OpsLog.LogEvent(elasticcache, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, elasticcache, logclient.ACT_CREATE, reason, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, elasticcache.Id, elasticcache.Name, api.ELASTIC_CACHE_STATUS_CREATE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: elasticcache, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } @@ -95,7 +99,11 @@ func (self *ElasticcacheCreateTask) OnSyncSecurityGroupCompleteFailed(ctx contex func (self *ElasticcacheCreateTask) OnElasticcacheCreateComplete(ctx context.Context, elasticcache *models.SElasticcache, data jsonutils.JSONObject) { elasticcache.SetStatus(self.GetUserCred(), api.ELASTIC_CACHE_STATUS_RUNNING, "") logclient.AddActionLogWithStartable(self, elasticcache, logclient.ACT_CREATE, "", self.UserCred, true) - notifyclient.NotifyWebhook(ctx, self.UserCred, elasticcache, notifyclient.ActionCreate) + // notifyclient.NotifyWebhook(ctx, self.UserCred, elasticcache, notifyclient.ActionCreate) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: elasticcache, + Action: notifyclient.ActionCreate, + }) self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/elasticcache_delete_task.go b/pkg/compute/tasks/elasticcache_delete_task.go index 87fb859bea..68b586debd 100644 --- a/pkg/compute/tasks/elasticcache_delete_task.go +++ b/pkg/compute/tasks/elasticcache_delete_task.go @@ -40,7 +40,11 @@ func (self *ElasticcacheDeleteTask) taskFail(ctx context.Context, elasticcache * elasticcache.SetStatus(self.GetUserCred(), api.ELASTIC_CACHE_STATUS_RELEASE_FAILED, reason.String()) db.OpsLog.LogEvent(elasticcache, db.ACT_DELOCATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, elasticcache, logclient.ACT_DELETE, reason, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, elasticcache.Id, elasticcache.Name, api.ELASTIC_CACHE_STATUS_RELEASE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: elasticcache, + Action: notifyclient.ActionDelete, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } @@ -62,7 +66,10 @@ func (self *ElasticcacheDeleteTask) OnInit(ctx context.Context, obj db.IStandalo ec.DeleteSubResources(ctx, self.UserCred) ec.SVirtualResourceBase.Delete(ctx, self.UserCred) logclient.AddActionLogWithStartable(self, ec, logclient.ACT_DELETE, "", self.UserCred, true) - notifyclient.NotifyWebhook(ctx, self.UserCred, ec, notifyclient.ActionDelete) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: ec, + Action: notifyclient.ActionDelete, + }) self.SetStageComplete(ctx, nil) } } diff --git a/pkg/compute/tasks/elasticcache_syncstatus_task.go b/pkg/compute/tasks/elasticcache_syncstatus_task.go index 2d3ee12419..a075a83da2 100644 --- a/pkg/compute/tasks/elasticcache_syncstatus_task.go +++ b/pkg/compute/tasks/elasticcache_syncstatus_task.go @@ -23,6 +23,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -40,6 +41,11 @@ func (self *ElasticcacheSyncstatusTask) taskFailed(ctx context.Context, cache *m self.SetStageFailed(ctx, err) db.OpsLog.LogEvent(cache, db.ACT_SYNC_STATUS, cache.GetShortDesc(ctx), self.GetUserCred()) logclient.AddActionLogWithContext(ctx, cache, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: cache, + Action: notifyclient.ActionSyncStatus, + IsFail: true, + }) } func (self *ElasticcacheSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { diff --git a/pkg/compute/tasks/guest_batch_create_task.go b/pkg/compute/tasks/guest_batch_create_task.go index 513bb6f60f..1c18e4f324 100644 --- a/pkg/compute/tasks/guest_batch_create_task.go +++ b/pkg/compute/tasks/guest_batch_create_task.go @@ -296,7 +296,11 @@ func (self *GuestBatchCreateTask) SaveScheduleResult(ctx context.Context, obj IS self.clearPendingUsage(ctx, guest) db.OpsLog.LogEvent(guest, db.ACT_ALLOCATE_FAIL, err, self.UserCred) logclient.AddActionLogWithStartable(self, obj, logclient.ACT_ALLOCATE, err, self.GetUserCred(), false) - notifyclient.NotifySystemErrorWithCtx(ctx, guest.Id, guest.Name, api.VM_CREATE_FAILED, err.Error()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionCreateBackupServer, + IsFail: true, + }) self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } } diff --git a/pkg/compute/tasks/guest_change_config_task.go b/pkg/compute/tasks/guest_change_config_task.go index 0a4730dab8..366f359943 100644 --- a/pkg/compute/tasks/guest_change_config_task.go +++ b/pkg/compute/tasks/guest_change_config_task.go @@ -335,7 +335,6 @@ func (self *GuestChangeConfigTask) OnGuestChangeCpuMemSpecCompleteFailed(ctx con func (self *GuestChangeConfigTask) OnGuestChangeCpuMemSpecFinish(ctx context.Context, guest *models.SGuest) { models.HostManager.ClearSchedDescCache(guest.HostId) self.SetStage("OnSyncConfigComplete", nil) - notifyclient.NotifyWebhook(ctx, self.UserCred, guest, notifyclient.ActionChangeConfig) err := guest.StartSyncTaskWithoutSyncstatus(ctx, self.UserCred, false, self.GetTaskId()) if err != nil { self.markStageFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("StartSyncstatus fail %s", err))) @@ -383,7 +382,11 @@ func (self *GuestChangeConfigTask) markStageFailed(ctx context.Context, guest *m guest.SetStatus(self.UserCred, api.VM_CHANGE_FLAVOR_FAIL, reason.String()) db.OpsLog.LogEvent(guest, db.ACT_CHANGE_FLAVOR_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_CHANGE_FLAVOR, reason, self.UserCred, false) - notifyclient.NotifyError(ctx, self.UserCred, guest.GetId(), guest.GetName(), logclient.ACT_VM_CHANGE_FLAVOR, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionChangeConfig, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } diff --git a/pkg/compute/tasks/guest_create_task.go b/pkg/compute/tasks/guest_create_task.go index 1f4b170edd..9209863b94 100644 --- a/pkg/compute/tasks/guest_create_task.go +++ b/pkg/compute/tasks/guest_create_task.go @@ -28,7 +28,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -70,7 +69,12 @@ func (self *GuestCreateTask) OnDiskPreparedFailed(ctx context.Context, obj db.IS guest.SetStatus(self.UserCred, api.VM_DISK_FAILED, "allocation failed") db.OpsLog.LogEvent(guest, db.ACT_ALLOCATE_FAIL, data, self.UserCred) logclient.AddActionLogWithStartable(self, guest, logclient.ACT_ALLOCATE, data, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, guest.Id, guest.Name, api.VM_DISK_FAILED, data.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionCreate, + IsFail: true, + }) + self.SetStageFailed(ctx, data) } @@ -99,7 +103,11 @@ func (self *GuestCreateTask) OnCdromPreparedFailed(ctx context.Context, obj db.I guest.SetStatus(self.UserCred, api.VM_DISK_FAILED, "") db.OpsLog.LogEvent(guest, db.ACT_ALLOCATE_FAIL, data, self.UserCred) logclient.AddActionLogWithStartable(self, guest, logclient.ACT_ALLOCATE, data, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, guest.Id, guest.Name, api.VM_DISK_FAILED, fmt.Sprintf("cdrom_failed %s", data)) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, data) } @@ -160,21 +168,18 @@ func (self *GuestCreateTask) OnDeployGuestDescComplete(ctx context.Context, obj } func (self *GuestCreateTask) notifyServerCreated(ctx context.Context, guest *models.SGuest) { - notifyclient.NotifyWebhook(ctx, self.UserCred, guest, notifyclient.ActionCreate) - guest.NotifyServerEvent( - ctx, self.UserCred, notifyclient.SERVER_CREATED, - notify.NotifyPriorityImportant, true, nil, false, - ) - guest.NotifyAdminServerEvent(ctx, notifyclient.SERVER_CREATED_ADMIN, notify.NotifyPriorityImportant) - // guest.EventNotify(ctx, self.UserCred, notifyclient.ActionCreate) + guest.EventNotify(ctx, self.UserCred, notifyclient.ActionCreate) } func (self *GuestCreateTask) OnDeployGuestDescCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) guest.SetStatus(self.UserCred, api.VM_DEPLOY_FAILED, "deploy_failed") db.OpsLog.LogEvent(guest, db.ACT_ALLOCATE_FAIL, data, self.UserCred) - logclient.AddActionLogWithStartable(self, guest, logclient.ACT_ALLOCATE, data, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, guest.Id, guest.Name, api.VM_DEPLOY_FAILED, data.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, data) } diff --git a/pkg/compute/tasks/guest_delete_backup_task.go b/pkg/compute/tasks/guest_delete_backup_task.go index 2df7e99073..022cf4c1a3 100644 --- a/pkg/compute/tasks/guest_delete_backup_task.go +++ b/pkg/compute/tasks/guest_delete_backup_task.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/httputils" "yunion.io/x/onecloud/pkg/util/logclient" @@ -40,6 +41,11 @@ func (self *GuestDeleteBackupTask) OnFail(ctx context.Context, guest *models.SGu logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_DELETE_BACKUP, reason, self.UserCred, false) db.OpsLog.LogEvent(guest, db.ACT_DELETE_BACKUP_FAILED, reason, self.UserCred) guest.SetStatus(self.UserCred, compute.VM_BACKUP_DELETE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionDelBackupServer, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } diff --git a/pkg/compute/tasks/guest_delete_task.go b/pkg/compute/tasks/guest_delete_task.go index 516d45d861..aa68303170 100644 --- a/pkg/compute/tasks/guest_delete_task.go +++ b/pkg/compute/tasks/guest_delete_task.go @@ -28,7 +28,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" - "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -307,6 +306,11 @@ func (self *GuestDeleteTask) OnFailed(ctx context.Context, guest *models.SGuest, guest.SetStatus(self.UserCred, api.VM_DELETE_FAIL, err.String()) db.OpsLog.LogEvent(guest, db.ACT_DELOCATE_FAIL, err, self.UserCred) logclient.AddActionLogWithStartable(self, guest, logclient.ACT_DELOCATE, err, self.UserCred, false) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionDelete, + IsFail: true, + }) self.SetStageFailed(ctx, err) } @@ -354,14 +358,5 @@ func (self *GuestDeleteTask) DeleteGuest(ctx context.Context, guest *models.SGue } func (self *GuestDeleteTask) NotifyServerDeleted(ctx context.Context, guest *models.SGuest) { - notifyclient.NotifyWebhook(ctx, self.UserCred, guest, notifyclient.ActionPendingDelete) - guest.NotifyServerEvent( - ctx, - self.UserCred, - notifyclient.SERVER_DELETED, - notify.NotifyPriorityImportant, - false, nil, false, - ) - guest.NotifyAdminServerEvent(ctx, notifyclient.SERVER_DELETED_ADMIN, notify.NotifyPriorityImportant) - // guest.EventNotify(ctx, self.UserCred, notifyclient.ActionPendingDelete) + guest.EventNotify(ctx, self.UserCred, notifyclient.ActionPendingDelete) } diff --git a/pkg/compute/tasks/guest_live_migrate_task.go b/pkg/compute/tasks/guest_live_migrate_task.go index 8a44467b39..525705c48e 100644 --- a/pkg/compute/tasks/guest_live_migrate_task.go +++ b/pkg/compute/tasks/guest_live_migrate_task.go @@ -470,6 +470,11 @@ func (self *GuestMigrateTask) TaskFailed(ctx context.Context, guest *models.SGue logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_MIGRATE, reason, self.UserCred, false) self.SetStageFailed(ctx, reason) notifyclient.NotifySystemErrorWithCtx(ctx, guest.Id, guest.Name, api.VM_MIGRATE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionMigrate, + IsFail: true, + }) } //ManagedGuestMigrateTask diff --git a/pkg/compute/tasks/guest_rebuild_root_task.go b/pkg/compute/tasks/guest_rebuild_root_task.go index a6e2431feb..b7e2118d80 100644 --- a/pkg/compute/tasks/guest_rebuild_root_task.go +++ b/pkg/compute/tasks/guest_rebuild_root_task.go @@ -27,7 +27,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -59,6 +58,11 @@ func (self *GuestRebuildRootTask) markFailed(ctx context.Context, guest *models. logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_REBUILD, reason, self.UserCred, false) guest.SetStatus(self.GetUserCred(), api.VM_REBUILD_ROOT_FAIL, reason.String()) self.SGuestBaseTask.SetStageFailed(ctx, reason) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: guest, + Action: notifyclient.ActionRebuildRoot, + IsFail: true, + }) gds := guest.CategorizeDisks() imageId, _ := self.Params.GetString("origin_image_id") _, err := db.Update(gds.Root, func() error { @@ -176,15 +180,7 @@ func (self *GuestRebuildRootTask) OnRebuildAllDisksComplete(ctx context.Context, } } db.OpsLog.LogEvent(guest, db.ACT_REBUILD_ROOT, "", self.UserCred) - notifyclient.NotifyWebhook(ctx, self.UserCred, guest, notifyclient.ActionRebuildRoot) - guest.NotifyServerEvent( - ctx, - self.UserCred, - notifyclient.SERVER_REBUILD_ROOT, - notify.NotifyPriorityImportant, - true, nil, false, - ) - // guest.EventNotify(ctx, self.UserCred, notifyclient.ActionRebuildRoot) + guest.EventNotify(ctx, self.UserCred, notifyclient.ActionRebuildRoot) self.SetStage("OnSyncStatusComplete", nil) guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) } diff --git a/pkg/compute/tasks/loadbalancer_create_task.go b/pkg/compute/tasks/loadbalancer_create_task.go index 62f03ca143..a42fbba0d2 100644 --- a/pkg/compute/tasks/loadbalancer_create_task.go +++ b/pkg/compute/tasks/loadbalancer_create_task.go @@ -41,6 +41,11 @@ func (self *LoadbalancerCreateTask) taskFail(ctx context.Context, lb *models.SLo db.OpsLog.LogEvent(lb, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, lb, logclient.ACT_CREATE, reason, self.UserCred, false) notifyclient.NotifySystemErrorWithCtx(ctx, lb.Id, lb.Name, api.LB_CREATE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: lb, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } @@ -71,7 +76,10 @@ func (self *LoadbalancerCreateTask) OnLoadbalancerCreateCompleteFailed(ctx conte func (self *LoadbalancerCreateTask) OnLoadbalancerStartComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { lb.SetStatus(self.GetUserCred(), api.LB_STATUS_ENABLED, "") - notifyclient.NotifyWebhook(ctx, self.UserCred, lb, notifyclient.ActionCreate) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: lb, + Action: notifyclient.ActionCreate, + }) self.SetStageComplete(ctx, nil) } diff --git a/pkg/compute/tasks/loadbalancer_delete_task.go b/pkg/compute/tasks/loadbalancer_delete_task.go index d1355d2b85..cd0700b485 100644 --- a/pkg/compute/tasks/loadbalancer_delete_task.go +++ b/pkg/compute/tasks/loadbalancer_delete_task.go @@ -40,7 +40,11 @@ func (self *LoadbalancerDeleteTask) taskFail(ctx context.Context, lb *models.SLo lb.SetStatus(self.GetUserCred(), api.LB_STATUS_DELETE_FAILED, reason.String()) db.OpsLog.LogEvent(lb, db.ACT_DELOCATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, lb, logclient.ACT_DELOCATE, reason, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, lb.Id, lb.Name, api.LB_STATUS_DELETE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: lb, + Action: notifyclient.ActionDelete, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } @@ -60,7 +64,11 @@ func (self *LoadbalancerDeleteTask) OnInit(ctx context.Context, obj db.IStandalo func (self *LoadbalancerDeleteTask) OnLoadbalancerDeleteComplete(ctx context.Context, lb *models.SLoadbalancer, data jsonutils.JSONObject) { db.OpsLog.LogEvent(lb, db.ACT_DELETE, lb.GetShortDesc(ctx), self.UserCred) logclient.AddActionLogWithStartable(self, lb, logclient.ACT_DELOCATE, nil, self.UserCred, true) - notifyclient.NotifyWebhook(ctx, self.UserCred, lb, notifyclient.ActionDelete) + // notifyclient.NotifyWebhook(ctx, self.UserCred, lb, notifyclient.ActionDelete) + notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{ + Obj: lb, + Action: notifyclient.ActionDelete, + }) lb.DeleteEip(ctx, self.UserCred) lb.LBPendingDelete(ctx, self.GetUserCred()) self.SetStageComplete(ctx, nil) diff --git a/pkg/compute/tasks/loadbalancer_listener_create_task.go b/pkg/compute/tasks/loadbalancer_listener_create_task.go index 156a2e8c29..fa9e86bfd1 100644 --- a/pkg/compute/tasks/loadbalancer_listener_create_task.go +++ b/pkg/compute/tasks/loadbalancer_listener_create_task.go @@ -159,7 +159,11 @@ func (self *LoadbalancerListenerCreateTask) taskFail(ctx context.Context, lblis lblis.SetStatus(self.GetUserCred(), api.LB_CREATE_FAILED, reason.String()) db.OpsLog.LogEvent(lblis, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, lblis, logclient.ACT_CREATE, reason, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, lblis.Id, lblis.Name, api.LB_CREATE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: lblis, + Action: notifyclient.ActionCreate, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } diff --git a/pkg/compute/tasks/loadbalancer_listener_delete_task.go b/pkg/compute/tasks/loadbalancer_listener_delete_task.go index 03305581a9..bc53b45e87 100644 --- a/pkg/compute/tasks/loadbalancer_listener_delete_task.go +++ b/pkg/compute/tasks/loadbalancer_listener_delete_task.go @@ -40,7 +40,11 @@ func (self *LoadbalancerListenerDeleteTask) taskFail(ctx context.Context, lblis lblis.SetStatus(self.GetUserCred(), api.LB_STATUS_DELETE_FAILED, reason.String()) db.OpsLog.LogEvent(lblis, db.ACT_DELOCATE_FAIL, reason, self.UserCred) logclient.AddActionLogWithStartable(self, lblis, logclient.ACT_DELOCATE, reason, self.UserCred, false) - notifyclient.NotifySystemErrorWithCtx(ctx, lblis.Id, lblis.Name, api.LB_STATUS_DELETE_FAILED, reason.String()) + notifyclient.EventNotify(ctx, self.GetUserCred(), notifyclient.SEventNotifyParam{ + Obj: lblis, + Action: notifyclient.ActionDelete, + IsFail: true, + }) self.SetStageFailed(ctx, reason) } diff --git a/pkg/mcclient/options/notify/receiver.go b/pkg/mcclient/options/notify/receiver.go index 38f2e77cd2..d4916ceb81 100644 --- a/pkg/mcclient/options/notify/receiver.go +++ b/pkg/mcclient/options/notify/receiver.go @@ -101,6 +101,19 @@ func (rv *ReceiverVerifyOptions) Params() (jsonutils.JSONObject, error) { return jsonutils.Marshal(rv.SreceiverVerifyOptions), nil } +type ReceiverEnableContactTypeInput struct { + ReceiverOptions + SreceiverEnableContactTypeInput +} + +func (re *ReceiverEnableContactTypeInput) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(re.SreceiverEnableContactTypeInput), nil +} + +type SreceiverEnableContactTypeInput struct { + EnabledContactTypes []string `help:"Enabled contact types"` +} + type ReceiverIntellijGetOptions struct { USERID string `help:"user id in keystone" json:"user_id"` CreateIfNo *bool `help:"create if receiver with UserId does not exist"` diff --git a/pkg/mcclient/options/notify/subscription.go b/pkg/mcclient/options/notify/subscription.go index 43ee4ba84f..aa96f23f49 100644 --- a/pkg/mcclient/options/notify/subscription.go +++ b/pkg/mcclient/options/notify/subscription.go @@ -40,14 +40,15 @@ func (so *TopicOptions) Params() (jsonutils.JSONObject, error) { } type SubscriberCreateOptions struct { - Name string `positional:"true"` - TopicId string `positional:"true"` - ResourceScope string `positional:"true" choices:"system|domain|project"` - Type string `positional:"true" choices:"receiver|robot|role"` - Receivers []string `help:"required if type is 'receiver'"` - Role string `help:"required if type is 'role'"` - RoleScope string `help:"required if type if 'role'"` - Robot string `help:"required if type if 'robot'"` + TopicId string `positional:"true"` + ResourceScope string `positional:"true" choices:"system|domain|project"` + ResourceAttributionId string `help:"project id or domain id of resource"` + Type string `positional:"true" choices:"receiver|robot|role"` + Receivers []string `help:"required if type is 'receiver'"` + Role string `help:"required if type is 'role'"` + RoleScope string `help:"required if type is 'role'"` + Robot string `help:"required if type is 'robot'"` + Scope string `positional:"true"` } func (sc *SubscriberCreateOptions) Params() (jsonutils.JSONObject, error) { @@ -59,6 +60,7 @@ type SubscriberListOptions struct { TopicId string ResourceScope string `choices:"system|domain|project"` Type string `choices:"receiver|robot|role"` + SCOPE string `choices:"system|domain"` } func (sl *SubscriberListOptions) Params() (jsonutils.JSONObject, error) { @@ -77,13 +79,16 @@ func (s *SubscriberOptions) Params() (jsonutils.JSONObject, error) { return nil, nil } -type SubscriberSetReceiverOptions struct { +type SubscriberChangeOptions struct { SubscriberOptions Receivers []string + Role string + RoleScope string + Robot string } -func (ssr *SubscriberSetReceiverOptions) Params() (jsonutils.JSONObject, error) { - params := jsonutils.NewDict() - params.Set("receivers", jsonutils.NewStringArray(ssr.Receivers)) +func (ssr *SubscriberChangeOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.Marshal(ssr) + params.(*jsonutils.JSONDict).Remove("id") return params, nil } diff --git a/pkg/notify/interface.go b/pkg/notify/interface.go index fcc4a9d837..41929669b0 100644 --- a/pkg/notify/interface.go +++ b/pkg/notify/interface.go @@ -19,6 +19,7 @@ import ( "yunion.io/x/pkg/errors" + notify_apis "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/notify/rpc/apis" ) @@ -59,15 +60,15 @@ type IServiceConfigStore interface { GetConfigs(service string) ([]SConfig, error) GetConfig(service, domainId string) (SConfig, error) SetConfig(service string, config SConfig) error - HasSystemConfig(service string) (bool, error) - BatchCheckConfig(service string, domainIds []string) ([]bool, error) + HasSystemConfig(service string) (bool, error) + BatchCheckConfig(service string, domainIds []string) ([]bool, error) } type SNotification struct { ContactType string Topic string Message string - Event string + Event notify_apis.SEvent AdvanceDays int } diff --git a/pkg/notify/models/config.go b/pkg/notify/models/config.go index 1c57783544..5523b41fff 100644 --- a/pkg/notify/models/config.go +++ b/pkg/notify/models/config.go @@ -74,7 +74,6 @@ func (cm *SConfigManager) ValidateCreateData(ctx context.Context, userCred mccli if err != nil { return input, err } - log.Infof("input: %s", jsonutils.Marshal(input)) if len(input.ProjectDomainId) > 0 { _, input.DomainizedResourceInput, err = db.ValidateDomainizedResourceInput(ctx, input.DomainizedResourceInput) if err != nil { diff --git a/pkg/notify/models/event.go b/pkg/notify/models/event.go new file mode 100644 index 0000000000..f120e8f454 --- /dev/null +++ b/pkg/notify/models/event.go @@ -0,0 +1,68 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" +) + +type SEventManager struct { + db.SStandaloneAnonResourceBaseManager +} + +var EventManager *SEventManager + +func init() { + EventManager = &SEventManager{ + SStandaloneAnonResourceBaseManager: db.NewStandaloneAnonResourceBaseManager( + SEvent{}, + "events_tbl", + "notifyevent", + "notifyevents", + ), + } + EventManager.SetVirtualObject(EventManager) +} + +type SEvent struct { + db.SStandaloneAnonResourceBase + + Message string + Event string `width:"32" nullable:"true"` + AdvanceDays int +} + +func (e *SEventManager) CreateEvent(ctx context.Context, event, message string, advanceDays int) (*SEvent, error) { + eve := &SEvent{ + Message: message, + Event: event, + AdvanceDays: advanceDays, + } + err := e.TableSpec().Insert(ctx, eve) + if err != nil { + return nil, err + } + return eve, nil +} + +func (e *SEventManager) GetEvent(id string) (*SEvent, error) { + model, err := e.FetchById(id) + if err != nil { + return nil, err + } + return model.(*SEvent), nil +} diff --git a/pkg/notify/models/event_template.go b/pkg/notify/models/event_template.go index 16bf7e27e9..e026e569da 100644 --- a/pkg/notify/models/event_template.go +++ b/pkg/notify/models/event_template.go @@ -79,12 +79,8 @@ func (lt *SLocalTemplateManager) detailsDisplay(resourceType string, details *js } func (lt *SLocalTemplateManager) FillWithTemplate(ctx context.Context, lang string, no notifyv2.SNotification) (params rpcapi.SendParams, err error) { - out := rpcapi.SendParams{} - event, err := parseEvent(no.Event) - if err != nil { - return out, errors.Wrapf(err, "unable to parse event %q", no.Event) - } - rtStr, aStr := event.ResourceType(), string(event.Action()) + out, event := rpcapi.SendParams{}, no.Event + rtStr, aStr, resultStr := event.ResourceType(), string(event.Action()), string(event.Result()) dict, err := jsonutils.ParseString(no.Message) if err != nil { return out, errors.Wrapf(err, "unable to parse json from %q", no.Message) @@ -92,10 +88,11 @@ func (lt *SLocalTemplateManager) FillWithTemplate(ctx context.Context, lang stri webhookMsg := jsonutils.NewDict() webhookMsg.Set("resource_type", jsonutils.NewString(rtStr)) webhookMsg.Set("action", jsonutils.NewString(aStr)) + webhookMsg.Set("result", jsonutils.NewString(resultStr)) webhookMsg.Set("resource_details", dict) if no.ContactType == api.WEBHOOK { return rpcapi.SendParams{ - Title: no.Topic, + Title: no.Event.StringWithDeli("_"), Message: webhookMsg.String(), }, nil } @@ -113,6 +110,10 @@ func (lt *SLocalTemplateManager) FillWithTemplate(ctx context.Context, lang stri if len(aDis) == 0 { aDis = aStr } + resultDis := notifyclientI18nTable.LookupByLang(tag, resultStr) + if len(resultDis) == 0 { + resultDis = resultStr + } lt.detailsDisplay(rtStr, dict.(*jsonutils.JSONDict), tag) @@ -120,6 +121,7 @@ func (lt *SLocalTemplateManager) FillWithTemplate(ctx context.Context, lang stri templateParams.Set("advance_days", jsonutils.NewInt(int64(no.AdvanceDays))) templateParams.Set("resource_type_display", jsonutils.NewString(rtDis)) templateParams.Set("action_display", jsonutils.NewString(aDis)) + templateParams.Set("result_display", jsonutils.NewString(resultDis)) // get title title, err := lt.fillWithTemplate(ctx, "title", no.ContactType, lang, event, templateParams) @@ -148,6 +150,19 @@ func (lt *SLocalTemplateManager) FillWithTemplate(ctx context.Context, lang stri var action2Topic = make(map[string]string, 0) +func specTopic(event api.SEvent) string { + switch event.Action() { + case api.ActionRebuildRoot, api.ActionChangeIpaddr, api.ActionResetPassword: + return string(api.ActionUpdate) + case api.ActionDelete: + switch event.ResourceType() { + case api.TOPIC_RESOURCE_BAREMETAL, api.TOPIC_RESOURCE_SERVER, api.TOPIC_RESOURCE_LOADBALANCER, api.TOPIC_RESOURCE_DBINSTANCE, api.TOPIC_RESOURCE_ELASTICCACHE: + return "DELETE_WITH_IP" + } + } + return "" +} + func init() { action2Topic[string(api.ActionRebuildRoot)] = string(api.ActionUpdate) action2Topic[string(api.ActionResetPassword)] = string(api.ActionUpdate) @@ -159,8 +174,11 @@ func (lt *SLocalTemplateManager) fillWithTemplate(ctx context.Context, titleOrCo tmpl *template.Template err error ) - actionStr := string(event.Action()) - for _, topic := range []string{event.String(), actionStr, action2Topic[actionStr], "common"} { + actionResultStr := event.ActionWithResult("_") + for _, topic := range []string{specTopic(event), event.StringWithDeli("_"), actionResultStr, "common"} { + if topic == "" { + continue + } tmpl, err = lt.getTemplate(ctx, titleOrContent, contactType, topic, lang) if errors.Cause(err) == errors.ErrNotFound { continue @@ -392,6 +410,11 @@ func init() { "scheduled task", "定时任务", }, + sI18nElme{ + api.TOPIC_RESOURCE_BAREMETAL, + "baremetal", + "裸金属", + }, sI18nElme{ string(api.ActionCreate), "created", @@ -437,5 +460,15 @@ func init() { "added to the recycle bin", "加入回收站", }, + sI18nElme{ + string(api.ResultFailed), + "failed", + "失败", + }, + sI18nElme{ + string(api.ResultSucceed), + "successfully", + "成功", + }, ) } diff --git a/pkg/notify/models/notification.go b/pkg/notify/models/notification.go index a0c1d99e72..69ddd9476d 100644 --- a/pkg/notify/models/notification.go +++ b/pkg/notify/models/notification.go @@ -67,12 +67,11 @@ type SNotification struct { Topic string `width:"128" nullable:"true" create:"required" search:"user"` Priority string `width:"16" nullable:"true" create:"optional" list:"user" get:"user"` // swagger:ignore - Message string `create:"required"` - ReceivedAt time.Time `nullable:"true" list:"user" get:"user"` - Event string `width:"32" nullable:"true" create:"optional"` - AdvanceDays int - SendTimes int - Tag string `width:"16" nullable:"true" index:"true" create:"optional"` + Message string `create:"required"` + ReceivedAt time.Time `nullable:"true" list:"user" get:"user"` + EventId string `width:"128" nullable:"true"` + SendTimes int + Tag string `width:"16" nullable:"true" index:"true" create:"optional"` } const ( @@ -220,7 +219,13 @@ func (nm *SNotificationManager) AllowPerformEventNotify(ctx context.Context, use // TODO: support project and domain func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NotificationManagerEventNotifyInput) (api.NotificationManagerEventNotifyOutput, error) { + log.Infof("default receiverIds: %s", input.ReceiverIds) var output api.NotificationManagerEventNotifyOutput + // check event + _, err := parseEvent(input.Event) + if err != nil { + return output, httperrors.NewInputParameterError("unable to parse event %q", input.Event) + } // contact type contactTypes := input.ContactTypes cts, err := ConfigManager.allContactType() @@ -245,10 +250,9 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred if err != nil { return output, errors.Wrap(err, "unable to get receive") } + log.Infof("receiver for topic: %s", receiverIds1) receiverIds = append(receiverIds, receiverIds1...) } - // deduplication - receiverIds = sets.NewString(receiverIds...).UnsortedList() // robot var robots []string @@ -262,19 +266,33 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred robots = append(robots, _robots...) } } - robots = intersection(robots, intersection(cts, RobotContactTypes)) + var webhookRobots []string if len(robots) > 0 { robots = sets.NewString(robots...).UnsortedList() + rs, err := RobotManager.FetchByIdOrNames(ctx, robots...) + if err != nil { + return output, errors.Wrap(err, "unable to get robots") + } + robots, webhookRobots = make([]string, 0, len(rs)), 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) + } + } } message := jsonutils.Marshal(input.ResourceDetails).String() + // append default receiver + receiverIds = append(receiverIds, input.ReceiverIds...) // fillter non-existed receiver receivers, err := ReceiverManager.FetchByIdOrNames(ctx, receiverIds...) if err != nil { return output, errors.Wrap(err, "unable to fetch receivers by ids") } - webconsoleContacts := make([]string, 0) + webconsoleContacts := sets.NewString() idSet := sets.NewString() for i := range receivers { idSet.Insert(receivers[i].Id) @@ -283,12 +301,18 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred if idSet.Has(re) { continue } - webconsoleContacts = append(webconsoleContacts, re) + webconsoleContacts.Insert(re) } receiverIds = idSet.UnsortedList() + // create event + event, err := EventManager.CreateEvent(ctx, input.Event, message, input.AdvanceDays) + if err != nil { + return output, errors.Wrap(err, "unable to create Event") + } + // webconsole - err = nm.create(ctx, userCred, api.WEBCONSOLE, receiverIds, webconsoleContacts, input.Priority, "", message, input.Event, input.AdvanceDays) + err = nm.create(ctx, userCred, api.WEBCONSOLE, receiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.Id) if err != nil { output.FailedList = append(output.FailedList, api.FailedElem{ ContactType: api.WEBCONSOLE, @@ -297,7 +321,10 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred } // normal contact type for _, ct := range contactTypes { - err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, "", message, input.Event, input.AdvanceDays) + if ct == api.MOBILE { + continue + } + err := nm.create(ctx, userCred, ct, receiverIds, nil, input.Priority, event.Id) if err != nil { output.FailedList = append(output.FailedList, api.FailedElem{ ContactType: ct, @@ -305,26 +332,64 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred }) } } + err = nm.createWithWebhookRobots(ctx, userCred, webhookRobots, input.Priority, event.Id) + if err != nil { + output.FailedList = append(output.FailedList, api.FailedElem{ + ContactType: api.WEBHOOK, + Reason: err.Error(), + }) + } // robot - err = nm.createWithRobots(ctx, userCred, robots, input.Priority, "", message, input.Event, input.AdvanceDays) - output.FailedList = append(output.FailedList, api.FailedElem{ - ContactType: api.ROBOT, - Reason: err.Error(), - }) + err = nm.createWithRobots(ctx, userCred, robots, input.Priority, event.Id) + if err != nil { + output.FailedList = append(output.FailedList, api.FailedElem{ + ContactType: api.ROBOT, + Reason: err.Error(), + }) + } return output, nil } -func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds []string, priority, topic, message, event string, advanceDays int) error { +func (nm *SNotificationManager) createWithWebhookRobots(ctx context.Context, userCred mcclient.TokenCredential, webhookRobotIds []string, priority, eventId string) error { + if len(webhookRobotIds) == 0 { + return nil + } + n := &SNotification{ + ContactType: api.WEBHOOK, + Priority: priority, + ReceivedAt: time.Now(), + EventId: eventId, + } + n.Id = db.DefaultUUIDGenerator() + for i := range webhookRobotIds { + _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, webhookRobotIds[i], n.Id) + if err != nil { + return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot") + } + } + err := nm.TableSpec().Insert(ctx, n) + if err != nil { + return errors.Wrap(err, "unable to insert Notification") + } + n.SetModelManager(nm, n) + task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "") + if err != nil { + log.Errorf("NotificationSendTask newTask error %v", err) + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds []string, priority, eventId string) error { if len(robotIds) == 0 { return nil } n := &SNotification{ ContactType: api.ROBOT, - Message: message, Priority: priority, ReceivedAt: time.Now(), - Event: event, - AdvanceDays: advanceDays, + EventId: eventId, } n.Id = db.DefaultUUIDGenerator() for i := range robotIds { @@ -347,7 +412,7 @@ func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred m return nil } -func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, topic, message, event string, advanceDays int) error { +func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds, contacts []string, priority, eventId string) error { if len(receiverIds)+len(contacts) == 0 { log.Infof("%s: no send", contactType) return nil @@ -355,11 +420,9 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To n := &SNotification{ ContactType: contactType, - Message: message, Priority: priority, ReceivedAt: time.Now(), - Event: event, - AdvanceDays: advanceDays, + EventId: eventId, } n.Id = db.DefaultUUIDGenerator() err := nm.TableSpec().Insert(ctx, n) @@ -454,6 +517,7 @@ func (n *SNotification) ReceiveDetails(userCred mcclient.TokenCredential, scope q.AppendField(subRQ.Field("name", "receiver_name")) q = q.Join(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id")))) } + q.DebugQuery() ret := make([]api.ReceiveDetail, 0, 2) err := q.All(&ret) if err != nil && errors.Cause(err) != sql.ErrNoRows { @@ -466,7 +530,11 @@ func (n *SNotification) ReceiveDetails(userCred mcclient.TokenCredential, scope func (n *SNotification) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) { // get title adn content lang := getLangSuffix(ctx) - p, err := n.TemplateStore().FillWithTemplate(ctx, lang, n.Notification()) + nn, err := n.Notification() + if err != nil { + return out, err + } + p, err := n.TemplateStore().FillWithTemplate(ctx, lang, nn) if err != nil { return out, err } @@ -482,14 +550,26 @@ func (n *SNotification) getMoreDetails(ctx context.Context, userCred mcclient.To return out, nil } -func (n *SNotification) Notification() notifyv2.SNotification { +func (n *SNotification) Notification() (notifyv2.SNotification, error) { + if n.EventId == "" { + return notifyv2.SNotification{ + ContactType: n.ContactType, + Topic: n.Topic, + Message: n.Message, + }, nil + } + event, err := EventManager.GetEvent(n.EventId) + if err != nil { + return notifyv2.SNotification{}, err + } + e, _ := parseEvent(event.Event) return notifyv2.SNotification{ ContactType: n.ContactType, Topic: n.Topic, - Message: n.Message, - Event: n.Event, - AdvanceDays: n.AdvanceDays, - } + Message: event.Message, + Event: e, + AdvanceDays: event.AdvanceDays, + }, nil } func (nm *SNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -738,7 +818,7 @@ func (nm *SNotificationManager) ReSend(ctx context.Context, userCred mcclient.To } func (n *SNotification) TemplateStore() notifyv2.ITemplateStore { - if len(n.Event) == 0 || n.ContactType == api.MOBILE { + if len(n.EventId) == 0 || n.ContactType == api.MOBILE { return TemplateManager } return LocalTemplateManager diff --git a/pkg/notify/models/receiver.go b/pkg/notify/models/receiver.go index 16f9d52274..da3684cf77 100644 --- a/pkg/notify/models/receiver.go +++ b/pkg/notify/models/receiver.go @@ -1294,3 +1294,28 @@ func (r *SReceiver) GetContact(cType string) (string, error) { func (r *SReceiver) GetDomainId() string { return r.DomainId } + +func (r *SReceiver) AllowPerformEnableContactType(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return r.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, r, "enable-contact-type") +} + +func (r *SReceiver) PerformEnableContactType(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ReceiverEnableContactTypeInput) (jsonutils.JSONObject, error) { + err := r.PullCache(false) + if err != nil { + return nil, errors.Wrap(err, "unable to pull cache") + } + err = r.SetEnabledContactTypes(input.EnabledContactTypes) + if err != nil { + return nil, errors.Wrap(err, "unable to set enabled contact types") + } + err = r.PushCache(ctx) + if err != nil { + return nil, errors.Wrap(err, "unable to push cache") + } + r.SetStatus(userCred, api.RECEIVER_STATUS_PULLING, "") + err = r.StartSubcontactPullTask(ctx, userCred, nil, "") + if err != nil { + log.Errorf("unable to StartSubcontactPullTask: %v", err) + } + return nil, nil +} diff --git a/pkg/notify/models/receiver_notification.go b/pkg/notify/models/receiver_notification.go index d04be9685b..1ba767b71d 100644 --- a/pkg/notify/models/receiver_notification.go +++ b/pkg/notify/models/receiver_notification.go @@ -2,9 +2,11 @@ package models import ( "context" + "net/http" "time" api "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -95,6 +97,13 @@ func (rnm *SReceiverNotificationManager) CreateContact(ctx context.Context, user return rn, rnm.TableSpec().Insert(ctx, rn) } +func (rnm *SReceiverNotificationManager) SetHandlerProcessTimeout(info *appsrv.SHandlerInfo, r *http.Request) time.Duration { + if r.Method == http.MethodGet && len(r.URL.Query().Get("export_keys")) > 0 { + return time.Hour * 2 + } + return -time.Second +} + // func (rn *SReceiverNotification) Receiver() (*SReceiver, error) { // q := ReceiverManager.Query().Equals("id", rn.ReceiverID) // var receiver SReceiver @@ -118,30 +127,30 @@ func (rn *SReceiverNotification) receiver() (*SReceiver, error) { } func (rn *SReceiverNotification) robot() (*SRobot, error) { - q := RobotManager.Query().Equals("id", rn.ReceiverID) - var robot SRobot - err := q.First(&robot) - if err != nil { - return nil, err - } - robot.SetModelManager(RobotManager, &robot) - return &robot, nil + q := RobotManager.Query().Equals("id", rn.ReceiverID) + var robot SRobot + err := q.First(&robot) + if err != nil { + return nil, err + } + robot.SetModelManager(RobotManager, &robot) + return &robot, nil } func (rn *SReceiverNotification) Receiver() (IReceiver, error) { switch rn.ReceiverType { case api.RECEIVER_TYPE_USER: - return rn.receiver() + return rn.receiver() case api.RECEIVER_TYPE_CONTACT: return &SContact{contact: rn.Contact}, nil case api.RECEIVER_TYPE_ROBOT: - return rn.robot() + return rn.robot() default: // compatible - if rn.ReceiverID != "" && rn.ReceiverID != ReceiverIdDefault { - return rn.receiver() - } - return &SContact{contact: rn.Contact}, nil + if rn.ReceiverID != "" && rn.ReceiverID != ReceiverIdDefault { + return rn.receiver() + } + return &SContact{contact: rn.Contact}, nil } } @@ -172,7 +181,7 @@ func (rn *SReceiverNotification) AfterSend(ctx context.Context, success bool, re type IReceiver interface { IsEnabled() bool - GetDomainId() string + GetDomainId() string IsEnabledContactType(string) (bool, error) IsVerifiedContactType(string) (bool, error) GetContact(string) (string, error) @@ -187,7 +196,7 @@ func (s SReceiverBase) IsEnabled() bool { } func (s SReceiverBase) GetDomainId() string { - return "" + return "" } func (s SReceiverBase) IsEnabledContactType(_ string) (bool, error) { diff --git a/pkg/notify/models/robot.go b/pkg/notify/models/robot.go index e0d855fb0e..d7ecb823e7 100644 --- a/pkg/notify/models/robot.go +++ b/pkg/notify/models/robot.go @@ -4,10 +4,12 @@ import ( "context" "strings" - "github.com/pkg/errors" "golang.org/x/text/language" + "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -211,6 +213,11 @@ func (r *SRobot) ValidateUpdateData(ctx context.Context, userCred mcclient.Token } func (r *SRobot) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + err := r.SSharableVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) + if err != nil { + return err + } + r.Enabled = tristate.True r.Status = api.ROBOT_STATUS_READY return nil } @@ -234,11 +241,11 @@ func (r *SRobot) IsEnabled() bool { } func (r *SRobot) IsEnabledContactType(ctype string) (bool, error) { - return ctype == api.ROBOT, nil + return ctype == api.ROBOT || ctype == api.WEBHOOK, nil } func (r *SRobot) IsVerifiedContactType(ctype string) (bool, error) { - return ctype == api.ROBOT, nil + return ctype == api.ROBOT || ctype == api.WEBHOOK, nil } func (r *SRobot) GetContact(ctype string) (string, error) { diff --git a/pkg/notify/models/subscriber.go b/pkg/notify/models/subscriber.go index 95c6e7fbfc..d251634650 100644 --- a/pkg/notify/models/subscriber.go +++ b/pkg/notify/models/subscriber.go @@ -25,6 +25,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -34,6 +35,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/rbacutils" "yunion.io/x/onecloud/pkg/util/stringutils2" ) @@ -41,7 +43,7 @@ var SubscriberManager *SSubscriberManager func init() { SubscriberManager = &SSubscriberManager{ - SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SStandaloneAnonResourceBaseManager: db.NewStandaloneAnonResourceBaseManager( SSubscriber{}, "subscriber_tbl", "subscriber", @@ -52,19 +54,22 @@ func init() { } type SSubscriberManager struct { - db.SVirtualResourceBaseManager + db.SStandaloneAnonResourceBaseManager db.SEnabledResourceBaseManager } type SSubscriber struct { - db.SVirtualResourceBase + db.SStandaloneAnonResourceBase db.SEnabledResourceBase - TopicID string `width:"128" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"` - Type string `width:"16" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user"` - Identification string `width:"128" charset:"ascii" nullable:"false" index:"true"` - RoleScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"` - ResourceScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user"` + TopicID string `width:"128" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user" create:"required"` + Type string `width:"16" charset:"ascii" nullable:"false" index:"true" get:"user" list:"user" create:"required"` + Identification string `width:"128" charset:"ascii" nullable:"false" index:"true"` + RoleScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user" create:"optional"` + ResourceScope string `width:"8" charset:"ascii" nullable:"false" get:"user" list:"user" create:"required"` + ResourceAttributionId string `width:"128" charset:"ascii" nullable:"false" get:"user" list:"user" create:"optional"` + Scope string `width:"128" charset:"ascii" nullable:"false" create:"required"` + DomainId string `width:"128" charset:"ascii" nullable:"false" create:"optional"` } func (sm *SSubscriberManager) validateReceivers(ctx context.Context, receivers []string) ([]string, error) { @@ -86,8 +91,25 @@ func (sm *SSubscriberManager) validateReceivers(ctx context.Context, receivers [ } func (sm *SSubscriberManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SubscriberCreateInput) (api.SubscriberCreateInput, error) { + log.Infof("before deal: %s", jsonutils.Marshal(input)) var err error - input.VirtualResourceCreateInput, err = sm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput) + // permission check + sSystem, sDomain := string(rbacutils.ScopeSystem), string(rbacutils.ScopeDomain) + switch input.Scope { + case sSystem: + allow := db.IsAdminAllowCreate(userCred, sm) + if !allow { + return input, httperrors.NewForbiddenError("The scope %s and the role of the operator do not match", input.Scope) + } + case sDomain: + allow := db.IsDomainAllowCreate(userCred, sm) + if !allow { + return input, httperrors.NewForbiddenError("The scope %s and the role of the operator do not match", input.Scope) + } + default: + return input, httperrors.NewInputParameterError("unknown scope %s", input.Scope) + } + input.StandaloneAnonResourceCreateInput, err = sm.SStandaloneAnonResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StandaloneAnonResourceCreateInput) if err != nil { return input, errors.Wrap(err, "SVirtualResourceBaseManager.ValidateCreateData") } @@ -100,6 +122,32 @@ func (sm *SSubscriberManager) ValidateCreateData(ctx context.Context, userCred m if !utils.IsInStringArray(input.ResourceScope, []string{api.SUBSCRIBER_SCOPE_SYSTEM, api.SUBSCRIBER_SCOPE_DOMAIN, api.SUBSCRIBER_SCOPE_PROJECT}) { return input, httperrors.NewInputParameterError("unknown resource_scope %q", input.ResourceScope) } + // resource Attribution Id + var domainId string + switch input.ResourceScope { + case api.SUBSCRIBER_SCOPE_SYSTEM: + input.ResourceAttributionId = "" + input.DomainId = "" + case api.SUBSCRIBER_SCOPE_PROJECT: + tenant, err := db.TenantCacheManager.FetchTenantByIdOrName(ctx, input.ResourceAttributionId) + if err != nil { + return input, errors.Wrapf(err, "unable to fetch project %s", input.ResourceAttributionId) + } + domainId = tenant.DomainId + input.DomainId = domainId + input.ResourceAttributionId = tenant.GetId() + case api.SUBSCRIBER_SCOPE_DOMAIN: + tenant, err := db.TenantCacheManager.FetchDomainByIdOrName(ctx, input.ResourceAttributionId) + if err != nil { + return input, errors.Wrapf(err, "unable to fetch domain %s", input.ResourceAttributionId) + } + domainId = tenant.DomainId + input.DomainId = domainId + input.ResourceAttributionId = tenant.DomainId + } + if input.Scope == sDomain && domainId != userCred.GetDomainId() { + return input, httperrors.NewForbiddenError("domain %s admin can't create subscriber for domain %s", userCred.GetDomainId(), domainId) + } input.TopicID = t.GetId() switch input.Type { case api.SUBSCRIBER_TYPE_RECEIVER: @@ -129,11 +177,12 @@ func (sm *SSubscriberManager) ValidateCreateData(ctx context.Context, userCred m default: return input, httperrors.NewInputParameterError("unkown type %q", input.Type) } + log.Infof("after deal input: %s", jsonutils.Marshal(input)) return input, nil } func (s *SSubscriber) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - err := s.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) + err := s.SStandaloneAnonResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) if err != nil { return errors.Wrap(err, "SVirtualResourceBase.CustomizeCreate") } @@ -150,12 +199,44 @@ func (s *SSubscriber) CustomizeCreate(ctx context.Context, userCred mcclient.Tok case api.SUBSCRIBER_TYPE_ROLE: s.Identification = input.Role } + s.Enabled = tristate.True return nil } +func (s *SSubscriber) AllowPerformChange(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (s *SSubscriber) PerformChange(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SubscriberChangeInput) (jsonutils.JSONObject, error) { + if s.Scope == string(rbacutils.ScopeSystem) { + if !db.IsAdminAllowUpdate(userCred, s) { + return nil, httperrors.NewForbiddenError("") + } + } else { + if !db.IsDomainAllowUpdate(userCred, s) { + return nil, httperrors.NewForbiddenError("") + } + if s.DomainId != userCred.GetDomainId() { + return nil, httperrors.NewForbiddenError("") + } + } + switch s.Type { + case api.SUBSCRIBER_TYPE_RECEIVER: + err := s.SetReceivers(ctx, input.Receivers) + if err != nil { + log.Errorf("unable to set receivers %s", input.Receivers) + } + case api.SUBSCRIBER_TYPE_ROBOT: + s.Identification = input.Robot + case api.SUBSCRIBER_TYPE_ROLE: + s.Identification = input.Role + } + return nil, nil +} + func (sm *SSubscriberManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.SubscriberListInput) (*sqlchemy.SQuery, error) { var err error - q, err = sm.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput) + q, err = sm.SStandaloneAnonResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneAnonResourceListInput) if err != nil { return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter") } @@ -163,6 +244,25 @@ func (sm *SSubscriberManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ if err != nil { return nil, errors.Wrap(err, "SEnabledResourceBaseManager.ListItemFilter") } + sSystem, sDomain := string(rbacutils.ScopeSystem), string(rbacutils.ScopeDomain) + if input.Scope == "" { + input.Scope = sSystem + } + switch input.Scope { + case sSystem: + allow := db.IsAdminAllowList(userCred, sm) + if !allow { + return nil, httperrors.NewForbiddenError("") + } + case sDomain: + allow := db.IsAdminAllowList(userCred, sm) + if !allow { + return nil, httperrors.NewForbiddenError("") + } + q = q.Equals("domain_id", userCred.GetDomainId()) + default: + return nil, httperrors.NewInputParameterError("unkown scope %s", input.Scope) + } if input.TopicID != "" { q = q.Equals("topic_id", input.TopicID) } @@ -177,10 +277,10 @@ func (sm *SSubscriberManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ func (sm *SSubscriberManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.SubscriberDetails { var err error - vRows := sm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + vRows := sm.SStandaloneAnonResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) rows := make([]api.SubscriberDetails, len(objs)) for i := range rows { - rows[i].VirtualResourceDetails = vRows[i] + rows[i].StandaloneAnonResourceDetails = vRows[i] s := objs[i].(*SSubscriber) switch s.Type { case api.SUBSCRIBER_TYPE_RECEIVER: @@ -203,6 +303,26 @@ func (sm *SSubscriberManager) FetchCustomizeColumns(ctx context.Context, userCre return rows } +func (s *SSubscriber) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + err := s.SStandaloneAnonResourceBase.CustomizeDelete(ctx, userCred, query, data) + if err != nil { + return err + } + if s.Scope == string(rbacutils.ScopeSystem) { + if !db.IsAdminAllowDelete(userCred, s) { + return httperrors.NewForbiddenError("") + } + } else { + if !db.IsDomainAllowDelete(userCred, s) { + return httperrors.NewForbiddenError("") + } + if s.DomainId != userCred.GetDomainId() { + return httperrors.NewForbiddenError("") + } + } + return nil +} + func (s *SSubscriber) receiverIdentifications() ([]api.Identification, error) { srSubq := SubscriberReceiverManager.Query().Equals("subscription_id", s.Id).SubQuery() rq := ReceiverManager.Query("id", "name") @@ -248,17 +368,17 @@ func (srm *SSubscriberManager) robot(tid, projectDomainId, projectId string) ([] } func (srm *SSubscriberManager) findSuitableOnes(tid, projectDomainId, projectId string, types ...string) ([]SSubscriber, error) { - q := srm.Query().Equals("subscription_id", tid) + q := srm.Query().Equals("topic_id", tid).IsTrue("enabled") q = q.Filter(sqlchemy.OR( sqlchemy.AND( - sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_PROJECT), - sqlchemy.Equals(q.Field("project_id"), projectId), + sqlchemy.Equals(q.Field("resource_scope"), api.SUBSCRIBER_SCOPE_PROJECT), + sqlchemy.Equals(q.Field("resource_attribution_id"), projectId), ), sqlchemy.AND( - sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_DOMAIN), - sqlchemy.Equals(q.Field("domain_id"), projectDomainId), + sqlchemy.Equals(q.Field("resource_scope"), api.SUBSCRIBER_SCOPE_DOMAIN), + sqlchemy.Equals(q.Field("resource_attribution_id"), projectDomainId), ), - sqlchemy.Equals(q.Field("resource_type"), api.SUBSCRIBER_SCOPE_SYSTEM), + sqlchemy.Equals(q.Field("resource_scope"), api.SUBSCRIBER_SCOPE_SYSTEM), )) switch len(types) { case 0: @@ -267,6 +387,7 @@ func (srm *SSubscriberManager) findSuitableOnes(tid, projectDomainId, projectId default: q = q.In("type", types) } + q.DebugQuery() srs := make([]SSubscriber, 0, 1) err := db.FetchModelObjects(srm, q, &srs) if err != nil { @@ -291,7 +412,7 @@ func (srm *SSubscriberManager) getReceiversSent(ctx context.Context, tid string, return nil, errors.Wrap(err, "unable to get receivers") } receivers = append(receivers, rIds...) - } else if sr.Type == api.SUBSCRIBER_TYPE_ROBOT { + } else if sr.Type == api.SUBSCRIBER_TYPE_ROLE { roleMap[sr.RoleScope] = append(roleMap[sr.RoleScope], sr.Identification) receivermap[sr.RoleScope] = &[]string{} } @@ -407,7 +528,7 @@ func (sr *SSubscriber) SetReceivers(ctx context.Context, receiverIds []string) e } func (s *SSubscriber) AllowPerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return s.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, s, "set-receiver") + return db.IsAdminAllowPerform(userCred, s, "set-receiver") } func (s *SSubscriber) PerformSetReceiver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SubscriberSetReceiverInput) (jsonutils.JSONObject, error) { diff --git a/pkg/notify/models/topic.go b/pkg/notify/models/topic.go index 1956c38442..9dfaea2bb6 100644 --- a/pkg/notify/models/topic.go +++ b/pkg/notify/models/topic.go @@ -18,8 +18,10 @@ import ( "context" "fmt" "strings" + "sync" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/sets" @@ -34,11 +36,16 @@ import ( ) func parseEvent(es string) (notify.SEvent, error) { + es = strings.ToLower(es) ess := strings.Split(es, notify.DelimiterInEvent) - if len(ess) != 2 { + if len(ess) != 2 && len(ess) != 3 { return notify.SEvent{}, fmt.Errorf("invalid event string %q", es) } - return notify.Event.WithResourceType(ess[0]).WithAction(notify.SAction(ess[1])), nil + event := notify.Event.WithResourceType(ess[0]).WithAction(notify.SAction(ess[1])) + if len(ess) == 3 { + event = event.WithResult(notify.SResult(ess[2])) + } + return event, nil } type STopicManager struct { @@ -67,18 +74,20 @@ type STopic struct { Type string `width:"20" nullable:"false" create:"required" update:"user" list:"user"` Resources uint64 `nullable:"false"` Actions uint32 `nullable:"false"` + Results uint8 `nullable:"false"` AdvanceDays int `nullable:"false"` } const ( - DefaultResourceCreateDelete = "resource create or delete" - DefaultResourceChangeConfig = "resource change config" - DefaultResourceUpdate = "resource update" - DefaultResourceReleaseDue1Day = "resource release due 1 day" - DefaultResourceReleaseDue3Day = "resource release due 3 day" - DefaultScheduledTaskExecute = "scheduled task execute" - DefaultScalingPolicyExecute = "scaling policy execute" - DefaultSnapshotPolicyExecute = "snapshot policy execute" + DefaultResourceCreateDelete = "resource create or delete" + DefaultResourceChangeConfig = "resource change config" + DefaultResourceUpdate = "resource update" + DefaultResourceReleaseDue1Day = "resource release due 1 day" + DefaultResourceReleaseDue3Day = "resource release due 3 day" + DefaultScheduledTaskExecute = "scheduled task execute" + DefaultScalingPolicyExecute = "scaling policy execute" + DefaultSnapshotPolicyExecute = "snapshot policy execute" + DefaultResourceOperationFailed = "resource operation failed" ) func (sm *STopicManager) InitializeData() error { @@ -91,6 +100,7 @@ func (sm *STopicManager) InitializeData() error { DefaultScheduledTaskExecute, DefaultScalingPolicyExecute, DefaultSnapshotPolicyExecute, + DefaultResourceOperationFailed, ) q := sm.Query() topics := make([]STopic, 0, initSNames.Len()) @@ -125,6 +135,7 @@ func (sm *STopicManager) InitializeData() error { notify.TOPIC_RESOURCE_BUCKET, notify.TOPIC_RESOURCE_DBINSTANCE, notify.TOPIC_RESOURCE_ELASTICCACHE, + notify.TOPIC_RESOURCE_BAREMETAL, ) t.addAction( notify.ActionCreate, @@ -189,6 +200,24 @@ func (sm *STopicManager) InitializeData() error { t.addResources(notify.TOPIC_RESOURCE_SNAPSHOTPOLICY) t.addAction(notify.ActionExecute) t.Type = notify.TOPIC_TYPE_AUTOMATED_PROCESS + case DefaultResourceOperationFailed: + t.addResources( + notify.TOPIC_RESOURCE_SERVER, + notify.TOPIC_RESOURCE_EIP, + notify.TOPIC_RESOURCE_LOADBALANCER, + notify.TOPIC_RESOURCE_DBINSTANCE, + notify.TOPIC_RESOURCE_ELASTICCACHE, + ) + t.addAction( + notify.ActionCreate, + notify.ActionSyncStatus, + notify.ActionRebuildRoot, + notify.ActionChangeConfig, + notify.ActionCreateBackupServer, + notify.ActionDelBackupServer, + notify.ActionMigrate, + ) + t.Type = notify.TOPIC_TYPE_RESOURCE } err := sm.TableSpec().Insert(ctx, t) if err != nil { @@ -293,7 +322,15 @@ func (sm *STopicManager) TopicsByEvent(eventStr string, advanceDays int) ([]STop return nil, errors.Wrapf(err, "unable to parse event %q", event) } resourceV := converter.resourceValue(event.ResourceType()) + if resourceV < 0 { + log.Warningf("unknown resource type: %s", event.ResourceType()) + return nil, nil + } actionV := converter.actionValue(event.Action()) + if actionV < 0 { + log.Warningf("unknown action type: %s", event.Action()) + return nil, nil + } q := sm.Query().Equals("advance_days", advanceDays) q = q.Filter(sqlchemy.GT(sqlchemy.AND_Val("", q.Field("resources"), 1<= len(rc.resourceList) { + r, ok := rc.value2Resource.Load(resourceValue) + if !ok { return "" } - return rc.resourceList[resourceValue] + return r.(string) } func (rc *sConverter) actionValue(action notify.SAction) int { - v, ok := rc.actionValueMap[action] + v, ok := rc.action2Value.Load(action) if !ok { return -1 } - return v + return v.(int) } func (rc *sConverter) action(actionValue int) notify.SAction { - if actionValue < 0 || actionValue >= len(rc.actionList) { + a, ok := rc.value2Action.Load(actionValue) + if !ok { return notify.SAction("") } - return rc.actionList[actionValue] + return a.(notify.SAction) } diff --git a/pkg/notify/rpc/apis/send_client.go b/pkg/notify/rpc/apis/send_client.go index 3b9a913af8..71248f9f95 100644 --- a/pkg/notify/rpc/apis/send_client.go +++ b/pkg/notify/rpc/apis/send_client.go @@ -42,15 +42,15 @@ func (c *SendNotificationClient) Send(ctx context.Context, in *SendParams, opts } func (c *SendNotificationClient) AddConfig(ctx context.Context, in *AddConfigInput, opts ...grpc.CallOption) (*Empty, error) { - ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) - defer cancel() - return c.sendAgentClient.AddConfig(ctx, in, opts...) + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.AddConfig(ctx, in, opts...) } func (c *SendNotificationClient) DeleteConfig(ctx context.Context, in *DeleteConfigInput, opts ...grpc.CallOption) (*Empty, error) { - ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) - defer cancel() - return c.sendAgentClient.DeleteConfig(ctx, in, opts...) + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.DeleteConfig(ctx, in, opts...) } func (c *SendNotificationClient) UpdateConfig(ctx context.Context, in *UpdateConfigInput, opts ...grpc.CallOption) (*Empty, error) { @@ -60,9 +60,9 @@ func (c *SendNotificationClient) UpdateConfig(ctx context.Context, in *UpdateCon } func (c *SendNotificationClient) CompleteConfig(ctx context.Context, in *CompleteConfigInput, opts ...grpc.CallOption) (*Empty, error) { - ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) - defer cancel() - return c.sendAgentClient.CompleteConfig(ctx, in, opts...) + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.CompleteConfig(ctx, in, opts...) } func (c *SendNotificationClient) ValidateConfig(ctx context.Context, in *ValidateConfigInput, opts ...grpc.CallOption) (*ValidateConfigReply, error) { @@ -78,9 +78,9 @@ func (c *SendNotificationClient) UseridByMobile(ctx context.Context, in *UseridB } func (c *SendNotificationClient) Ready(ctx context.Context, in *ReadyInput, opts ...grpc.CallOption) (*ReadyOutput, error) { - ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) - defer cancel() - return c.sendAgentClient.Ready(ctx, in, opts...) + ctx, cancel := context.WithTimeout(ctx, c.CallTimeout) + defer cancel() + return c.sendAgentClient.Ready(ctx, in, opts...) } func (c *SendNotificationClient) BatchSend(ctx context.Context, in *BatchSendParams, opts ...grpc.CallOption) (*BatchSendReply, error) { diff --git a/pkg/notify/rpc/apis/send_server.pb.go b/pkg/notify/rpc/apis/send_server.pb.go index cb7e2c8626..112f99484a 100644 --- a/pkg/notify/rpc/apis/send_server.pb.go +++ b/pkg/notify/rpc/apis/send_server.pb.go @@ -6,11 +6,12 @@ package apis import ( context "context" fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - math "math" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/notify/rpc/send.go b/pkg/notify/rpc/send.go index 271e48e64b..86bf0c4184 100644 --- a/pkg/notify/rpc/send.go +++ b/pkg/notify/rpc/send.go @@ -257,7 +257,7 @@ func (self *SRpcService) ContactByMobile(ctx context.Context, mobile, serviceNam if s.Code() == codes.NotFound { return "", errors.Wrap(notifyv2.ErrNoSuchMobile, s.Message()) } - if s.Code() == codes.PermissionDenied { + if s.Code() == codes.FailedPrecondition { return "", errors.Wrap(notifyv2.ErrIncompleteConfig, s.Message()) } return "", err diff --git a/pkg/notify/service/handlers.go b/pkg/notify/service/handlers.go index 048b0e4cb0..9aa9ecd278 100644 --- a/pkg/notify/service/handlers.go +++ b/pkg/notify/service/handlers.go @@ -39,6 +39,7 @@ func InitHandlers(app *appsrv.Application) { models.SubContactManager, db.SharedResourceManager, models.VerificationManager, + models.EventManager, } { db.RegisterModelManager(manager) } diff --git a/pkg/notify/tasks/notifications_send_task.go b/pkg/notify/tasks/notifications_send_task.go index add8bfbc1a..ee5c02246b 100644 --- a/pkg/notify/tasks/notifications_send_task.go +++ b/pkg/notify/tasks/notifications_send_task.go @@ -145,7 +145,11 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone } // send - p, err := notification.TemplateStore().FillWithTemplate(ctx, lang, notification.Notification()) + nn, err := notification.Notification() + if err != nil { + self.taskFailed(ctx, notification, err.Error(), false) + } + p, err := notification.TemplateStore().FillWithTemplate(ctx, lang, nn) if err != nil { self.taskFailed(ctx, notification, err.Error(), false) } @@ -199,7 +203,7 @@ type FailedReceiverSpec struct { func (self *NotificationSendTask) batchSend(ctx context.Context, contactType string, receivers []ReceiverSpec, params rpcapi.SendParams) (fails []FailedReceiverSpec, err error) { log.Infof("contactType: %s, receivers: %s, params: %s", contactType, receivers, jsonutils.Marshal(params)) - if contactType != apis.ROBOT { + if contactType != apis.ROBOT && contactType != apis.WEBHOOK { return self._batchSend(ctx, contactType, receivers, func(res []*rpcapi.SReceiver) ([]*rpcapi.FailedRecord, error) { return models.NotifyService.BatchSend(ctx, contactType, rpcapi.BatchSendParams{ Receivers: res, diff --git a/pkg/notify/tasks/repull_subcontact_task.go b/pkg/notify/tasks/repull_subcontact_task.go index 961e896534..802bf40c91 100644 --- a/pkg/notify/tasks/repull_subcontact_task.go +++ b/pkg/notify/tasks/repull_subcontact_task.go @@ -24,11 +24,11 @@ import ( "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" + "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/notify/models" - "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -62,13 +62,13 @@ func (self *RepullSuncontactTask) OnInit(ctx context.Context, obj db.IStandalone } subq := models.SubContactManager.Query("receiver_id").Equals("type", config.Type).SubQuery() q := models.ReceiverManager.Query() - if config.Attribution == notify.CONFIG_ATTRIBUTION_DOMAIN { - q = q.Equals("domain_id", config.DomainId) - } else { - // The system-level config update should not affect the receiver under the domain with config - configq := models.ConfigManager.Query("domain_id").Equals("attribution", notify.CONFIG_ATTRIBUTION_DOMAIN).SubQuery() - q = q.Join(configq, sqlchemy.NotEquals(q.Field("domain_id"), configq.Field("domain_id"))) - } + if config.Attribution == notify.CONFIG_ATTRIBUTION_DOMAIN { + q = q.Equals("domain_id", config.DomainId) + } else { + // The system-level config update should not affect the receiver under the domain with config + configq := models.ConfigManager.Query("domain_id").Equals("attribution", notify.CONFIG_ATTRIBUTION_DOMAIN).SubQuery() + q = q.Join(configq, sqlchemy.NotEquals(q.Field("domain_id"), configq.Field("domain_id"))) + } q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("receiver_id"))) rs := make([]models.SReceiver, 0) err := db.FetchModelObjects(models.ReceiverManager, q, &rs) diff --git a/pkg/notify/tasks/verification_send_task.go b/pkg/notify/tasks/verification_send_task.go index 6cb53ee31e..faadaaec55 100644 --- a/pkg/notify/tasks/verification_send_task.go +++ b/pkg/notify/tasks/verification_send_task.go @@ -82,10 +82,10 @@ func (self *VerificationSendTask) OnInit(ctx context.Context, obj db.IStandalone self.taskFailed(ctx, receiver, err.Error()) return } - param.Receiver = &apis.SReceiver{ - Contact: contact, - DomainId: receiver.DomainId, - } + param.Receiver = &apis.SReceiver{ + Contact: contact, + DomainId: receiver.DomainId, + } err = models.NotifyService.Send(ctx, contactType, param) if err != nil { self.taskFailed(ctx, receiver, err.Error()) From 83ac6296524d2d554f6e244b5dda53e080caa3bc Mon Sep 17 00:00:00 2001 From: rainzm Date: Thu, 17 Jun 2021 19:21:15 +0800 Subject: [PATCH 6/7] feat(notify): compatible with webhook configuration --- pkg/notify/models/robot.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pkg/notify/models/robot.go b/pkg/notify/models/robot.go index d7ecb823e7..00afeb6148 100644 --- a/pkg/notify/models/robot.go +++ b/pkg/notify/models/robot.go @@ -100,12 +100,16 @@ func (rm *SRobotManager) InitializeData() error { robots = append(robots, robot) } ctx := context.Background() + var webhookRobotId string // insert new robot for i := range robots { err := rm.TableSpec().Insert(ctx, &robots[i]) if err != nil { return err } + if robots[i].Type == api.ROBOT_TYPE_WEBHOOK { + webhookRobotId = robots[i].Id + } } // delete old configs for i := range configs { @@ -117,6 +121,30 @@ func (rm *SRobotManager) InitializeData() error { return err } } + // add webhook Robot to subscriber + if len(webhookRobotId) > 0 { + topics := make([]STopic, 0, 3) + q = TopicManager.Query().In("name", []string{DefaultResourceCreateDelete, DefaultResourceChangeConfig, DefaultResourceUpdate}) + err := db.FetchModelObjects(TopicManager, q, &topics) + if err != nil { + return errors.Wrap(err, "unable to fetch topics") + } + // create subscribers + for i := range topics { + subscriber := SSubscriber{ + TopicID: topics[i].Id, + Type: api.SUBSCRIBER_TYPE_ROBOT, + Identification: webhookRobotId, + ResourceScope: "system", + Scope: "system", + } + subscriber.Enabled = tristate.True + err := SubscriberManager.TableSpec().Insert(ctx, &subscriber) + if err != nil { + return errors.Wrapf(err, "unable to create subscriber %s", jsonutils.Marshal(subscriber)) + } + } + } return nil } From cb335c108630b3bcb4981e62c8d94429137b75a1 Mon Sep 17 00:00:00 2001 From: rainzm Date: Thu, 17 Jun 2021 19:22:01 +0800 Subject: [PATCH 7/7] feat(region): remove webhook notify in guest.PostUpdate --- pkg/compute/models/guests.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 746ee51995..5fbf67fd98 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -47,7 +47,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" - "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/cloudcommon/userdata" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -1682,8 +1681,6 @@ func (self *SGuest) PostUpdate(ctx context.Context, userCred mcclient.TokenCrede log.Errorf("StartRemoteUpdateTask fail: %s", err) } } - // notify webhook - notifyclient.NotifyWebhook(ctx, userCred, self, notifyclient.ActionUpdate) } func (manager *SGuestManager) checkCreateQuota(