mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
notify module in Go finished.
This commit is contained in:
@@ -29,8 +29,8 @@ func init() {
|
||||
*/
|
||||
type ContactsUpdateOptions struct {
|
||||
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
|
||||
CONTACTTYPE string `help:"The contact type" choices:"email|mobile"`
|
||||
CONTACT string `help:"The contacts details mobile number or email address, if set it the empty str means delete"`
|
||||
CONTACTTYPE string `help:"The contact type email|mobile" choices:"email|mobile|dingtalk"`
|
||||
CONTACT string `help:"The contacts details mobile number or email address or dingtalk's userid, if set it the empty str means delete"`
|
||||
Status string `help:"Enabled or disabled contact status" choices:"enable|disable"`
|
||||
}
|
||||
R(&ContactsUpdateOptions{}, "contact-update", "Create, delete or update contact for user", func(s *mcclient.ClientSession, args *ContactsUpdateOptions) error {
|
||||
@@ -40,9 +40,9 @@ func init() {
|
||||
tmpObj.Add(jsonutils.NewString(args.CONTACT), "contact")
|
||||
if len(args.Status) > 0 {
|
||||
if args.Status == "disable" {
|
||||
tmpObj.Add(jsonutils.JSONFalse, "enabled")
|
||||
tmpObj.Add(jsonutils.NewInt(0), "enabled")
|
||||
} else {
|
||||
tmpObj.Add(jsonutils.JSONTrue, "enabled")
|
||||
tmpObj.Add(jsonutils.NewInt(1), "enabled")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func init() {
|
||||
|
||||
type ContactsDeleteOptions struct {
|
||||
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
|
||||
CONTACTTYPE string `help:"The contact type" choices:"email|mobile"`
|
||||
CONTACTTYPE string `help:"The contact type email|mobile|dingtalk" choices:"email|mobile|dingtalk"`
|
||||
}
|
||||
R(&ContactsDeleteOptions{}, "contact-delete", "Delete contact for user", func(s *mcclient.ClientSession, args *ContactsDeleteOptions) error {
|
||||
arr := jsonutils.NewArray()
|
||||
|
||||
@@ -93,7 +93,7 @@ func init() {
|
||||
STATUS string `help:"Notification send status" choices:"sent_ok|send_fail"`
|
||||
Remark string `help:"Remark or description of the operation or fail reason"`
|
||||
}
|
||||
R(&NotificationUpdateCallbackOptions{}, "notification-update-callback", "Update send status of the notification task", func(s *mcclient.ClientSession, args *NotificationUpdateCallbackOptions) error {
|
||||
R(&NotificationUpdateCallbackOptions{}, "notification-update-callback", "UpdateItem send status of the notification task", func(s *mcclient.ClientSession, args *NotificationUpdateCallbackOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.STATUS), "status")
|
||||
if len(args.Remark) > 0 {
|
||||
|
||||
59
cmd/climc/shell/notify.go
Normal file
59
cmd/climc/shell/notify.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 shell
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
type ConfigCreate2Options struct {
|
||||
CONTACTTYPE string `help:"contact type (email|sms_aliyun or others)"`
|
||||
CONFIGS []string `help:"config (k, v)"`
|
||||
}
|
||||
R(&ConfigCreate2Options{}, "notify-config-update", "config update, example: notify_config-update email mail.smtp.hostname hostname mail.smtp.hostport 123.", func(s *mcclient.ClientSession, args *ConfigCreate2Options) error {
|
||||
tmp := jsonutils.NewDict()
|
||||
for i := 0; i+1 < len(args.CONFIGS); i += 2 {
|
||||
tmp.Add(jsonutils.NewString(args.CONFIGS[i+1]), args.CONFIGS[i])
|
||||
}
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(tmp, args.CONTACTTYPE)
|
||||
modules.Configs.Create(s, body)
|
||||
return nil
|
||||
})
|
||||
|
||||
type ConfigGet2Options struct {
|
||||
TYPE string `help:"contact type (email|sms_aliyun or others)"`
|
||||
}
|
||||
R(&ConfigGet2Options{}, "notify-config-show", "config show", func(s *mcclient.ClientSession, args *ConfigGet2Options) error {
|
||||
result, err := modules.Configs.Get(s, args.TYPE, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
R(&ConfigGet2Options{}, "notify-config-delete", "config delete", func(s *mcclient.ClientSession, args *ConfigGet2Options) error {
|
||||
result, err := modules.Configs.Delete(s, args.TYPE, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
9
cmd/notify/main.go
Normal file
9
cmd/notify/main.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/notify"
|
||||
)
|
||||
|
||||
func main() {
|
||||
notify.StartService()
|
||||
}
|
||||
@@ -3047,7 +3047,7 @@ func (self *SGuest) doSaveRenewInfo(ctx context.Context, userCred mcclient.Token
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Update error %s", err)
|
||||
log.Errorf("UpdateItem error %s", err)
|
||||
return err
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_RENEW, self.GetShortDesc(ctx), userCred)
|
||||
|
||||
@@ -404,4 +404,4 @@ func (self *SSnapshotPolicy) preCheck(
|
||||
return nil, httperrors.NewNotFoundError("Disks %v not found", notFoundDisks)
|
||||
}
|
||||
return diskIds, nil
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* config.go - config file definitions
|
||||
* mod_config.go - config file definitions
|
||||
*
|
||||
* @author Yaroslav Pogrebnyak <yyyaroslav@gmail.com>
|
||||
* @author Gene Ponomarenko <kikomdev@gmail.com>
|
||||
|
||||
17
pkg/mcclient/modules/mod_notify.go
Normal file
17
pkg/mcclient/modules/mod_notify.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package modules
|
||||
|
||||
type ConfigsManager struct {
|
||||
ResourceManager
|
||||
}
|
||||
|
||||
var (
|
||||
Configs ConfigsManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Configs = ConfigsManager{NewNotifyManager("config", "configs",
|
||||
[]string{},
|
||||
[]string{})}
|
||||
|
||||
register(&Configs)
|
||||
}
|
||||
184
pkg/notify/compatible_config.go
Normal file
184
pkg/notify/compatible_config.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
const (
|
||||
EMAIL_KEYWORDPLURAL = "email_configs"
|
||||
EMAIL_KEYWORD = "email_config"
|
||||
EMAIL = "email"
|
||||
|
||||
SMS_KEYWORDPLURAL = "sms_configs"
|
||||
SMS_KEYWORD = "sms_config"
|
||||
SMS = "mobile"
|
||||
)
|
||||
|
||||
func emailConfigDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// do not need modify
|
||||
manager, _, _, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": EMAIL,
|
||||
}
|
||||
err := manager.DeleteConfig(ctx, params)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func emailConfigGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, query, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": EMAIL,
|
||||
}
|
||||
ret, err := manager.GetConfig(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
// hostport should be int and ssl_global should be bool
|
||||
newDataDict := make(map[string]interface{})
|
||||
data, _ := ret.Get("config")
|
||||
dataDict := data.(*jsonutils.JSONDict)
|
||||
dataDict = database2Display(dataDict)
|
||||
for _, k := range dataDict.SortedKeys() {
|
||||
tmp, _ := dataDict.GetString(k)
|
||||
switch k {
|
||||
case "hostport":
|
||||
port, _ := strconv.Atoi(tmp)
|
||||
newDataDict[k] = port
|
||||
case "ssl_global":
|
||||
ssl, _ := strconv.ParseBool(tmp)
|
||||
newDataDict[k] = ssl
|
||||
default:
|
||||
newDataDict[k] = tmp
|
||||
}
|
||||
}
|
||||
appsrv.SendJSON(w, jsonutils.Marshal(map[string]map[string]interface{}{
|
||||
EMAIL_KEYWORD: newDataDict,
|
||||
}))
|
||||
}
|
||||
|
||||
func dispaly2Database(dict *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
keys := dict.SortedKeys()
|
||||
newKey := ""
|
||||
for _, key := range keys {
|
||||
switch key {
|
||||
case "username", "password":
|
||||
newKey = "mail." + key
|
||||
case "hostname", "hostport":
|
||||
newKey = "mail.smtp." + key
|
||||
case "ssl_global":
|
||||
newKey = "mail.global.ssl"
|
||||
}
|
||||
v, _ := dict.Get(key)
|
||||
dict.Add(v, newKey)
|
||||
dict.Remove(key)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func database2Display(dict *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
keys := dict.SortedKeys()
|
||||
newKey := ""
|
||||
for _, key := range keys {
|
||||
switch key {
|
||||
case "mail.username", "mail.password":
|
||||
newKey = key[5:]
|
||||
case "mail.smtp.hostname", "mail.smtp.hostport":
|
||||
newKey = key[10:]
|
||||
case "mail.global.ssl":
|
||||
newKey = "ssl_global"
|
||||
}
|
||||
v, _ := dict.Get(key)
|
||||
dict.Add(v, newKey)
|
||||
dict.Remove(key)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func emailConfigUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
body, _ = body.Get(EMAIL_KEYWORD)
|
||||
bodyRet := jsonutils.DeepCopy(body)
|
||||
bodyDict := body.(*jsonutils.JSONDict)
|
||||
newBody := jsonutils.NewDict()
|
||||
newBody.Add(dispaly2Database(bodyDict), EMAIL)
|
||||
err := manager.UpdateConfig(ctx, newBody)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
data := jsonutils.NewDict()
|
||||
data.Add(jsonutils.NewInt(200), "code")
|
||||
data.Add(jsonutils.NewString("OK"), "title")
|
||||
data.Add(bodyRet, "message")
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(data, EMAIL_KEYWORD)
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
func smsConfigDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// do not need modify
|
||||
manager, _, _, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": SMS,
|
||||
}
|
||||
err := manager.DeleteConfig(ctx, params)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func smsConfigGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, query, _ := fetchEnv(ctx, w, r)
|
||||
params := map[string]string{
|
||||
"<type>": SMS,
|
||||
}
|
||||
ret, err := manager.GetConfig(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
// modify
|
||||
data, _ := ret.Get("config")
|
||||
appsrv.SendJSON(w, jsonutils.Marshal(map[string]jsonutils.JSONObject{
|
||||
SMS_KEYWORD: data,
|
||||
}))
|
||||
}
|
||||
|
||||
func smsConfigUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
body, _ = body.Get(SMS_KEYWORD)
|
||||
newBody := jsonutils.NewDict()
|
||||
newBody.Add(body, SMS)
|
||||
err := manager.UpdateConfig(ctx, newBody)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
data := jsonutils.NewDict()
|
||||
data.Add(jsonutils.NewInt(200), "code")
|
||||
data.Add(jsonutils.NewString("OK"), "title")
|
||||
data.Add(body, "message")
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(data, SMS_KEYWORD)
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
513
pkg/notify/dispatcher.go
Normal file
513
pkg/notify/dispatcher.go
Normal file
@@ -0,0 +1,513 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appctx"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
)
|
||||
|
||||
// NotifyModelDispatcher is designed to complete some function that db.DBModelDispatcher can't.
|
||||
// The apis of notify module has a certain degree of particularity so that we can't use common function.
|
||||
type NotifyModelDispatcher struct {
|
||||
db.DBModelDispatcher
|
||||
}
|
||||
|
||||
func NewNotifyModelDispatcher(manager db.IModelManager) *NotifyModelDispatcher {
|
||||
return &NotifyModelDispatcher{*db.NewModelHandler(manager)}
|
||||
}
|
||||
|
||||
func (self *NotifyModelDispatcher) GetConfig(ctx context.Context, params map[string]string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
listResult, err := self.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyVs := make(map[string]string)
|
||||
for _, ret := range listResult.Data {
|
||||
key, _ := ret.GetString("key_text")
|
||||
value, _ := ret.GetString("value_text")
|
||||
keyVs[key] = value
|
||||
}
|
||||
return jsonutils.Marshal(map[string]map[string]string{
|
||||
models.ConfigManager.Keyword(): keyVs,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (self *NotifyModelDispatcher) DeleteConfig(ctx context.Context, params map[string]string) error {
|
||||
contactType := params["<type>"]
|
||||
configs, err := models.ConfigManager.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Get Config by contactType failed")
|
||||
}
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
for i := range configs {
|
||||
err = DeleteItem(models.ConfigManager, &configs[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Delete part of old one, so please input new data again.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateConfig update config and restart corresponding send service.
|
||||
func (self *NotifyModelDispatcher) UpdateConfig(ctx context.Context, body jsonutils.JSONObject) error {
|
||||
data := body.(*jsonutils.JSONDict)
|
||||
contactType := data.SortedKeys()[0]
|
||||
originData, err := models.ConfigManager.GetVauleByType(contactType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, _ := data.Get(contactType)
|
||||
data = tmp.(*jsonutils.JSONDict)
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
// If no config of type 'contactType' in database, create news.
|
||||
// Else delete original ones and create news.
|
||||
if len(originData) != 0 {
|
||||
// delete original
|
||||
configs, err := models.ConfigManager.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Get Config by contactType failed")
|
||||
}
|
||||
for i := range configs {
|
||||
err = DeleteItem(models.ConfigManager, &configs[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Delete part of old one, so please input new data again.")
|
||||
}
|
||||
}
|
||||
}
|
||||
// create
|
||||
for _, key := range data.SortedKeys() {
|
||||
createData := jsonutils.NewDict()
|
||||
tmp, _ = data.Get(key)
|
||||
createData.Add(tmp, "value_text")
|
||||
createData.Add(jsonutils.NewString(key), "key_text")
|
||||
createData.Add(jsonutils.NewString(contactType), "type")
|
||||
_, err := self.Create(ctx, jsonutils.JSONNull, createData, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Create config (%s, %s, %s) failed", contactType, key, tmp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNotification create new notifications and send them through rpc.RpcService.
|
||||
// If data contains 'gid' field, that means that send message to all users in group.
|
||||
// Else send messager to user whose uid equals 'uid' in data.
|
||||
func (self *NotifyModelDispatcher) CreateNotification(ctx context.Context, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
// Get all contacts info of group if data contains "gid".
|
||||
// If no contact, return ErrContactNotFound.
|
||||
contactType, _ := data.GetString("contact_type")
|
||||
group, id := false, ""
|
||||
if data.Contains("gid") {
|
||||
group = true
|
||||
id, _ = data.GetString("gid")
|
||||
} else {
|
||||
id, _ = data.GetString("uid")
|
||||
}
|
||||
contacts, err := models.ContactManager.GetAllNotify(id, contactType, group)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(errors.Wrap(err, "get all contacts error"))
|
||||
}
|
||||
|
||||
notificationIDs, err := models.NotificationManager.BatchCreate(ctx, data, contacts)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(jsonutils.NewStringArray(notificationIDs), "notifications")
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Verify process:
|
||||
// 1.fetch verify by ID; 2.check that if verify is expired;
|
||||
// 3.if not check that if token is correct and update status of contact whose id is verify's CID
|
||||
// 4.otherwise generate a new verify and delete old one
|
||||
func (self *NotifyModelDispatcher) Verify(ctx context.Context, params map[string]string, query jsonutils.JSONObject) error {
|
||||
processID := params["<id>"]
|
||||
token, _ := query.GetString("token")
|
||||
manager := models.VerifyManager
|
||||
verifys, err := manager.FetchByID(processID)
|
||||
if err != nil {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
var verifition models.SVerify
|
||||
if len(verifys) == 0 {
|
||||
return httperrors.NewNotFoundError("%s verify record not found", processID)
|
||||
}
|
||||
current, have := time.Now(), false
|
||||
for i := range verifys {
|
||||
if current.Before(verifys[i].ExpireAt) {
|
||||
verifition = verifys[i]
|
||||
have = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !have {
|
||||
return httperrors.NewBadRequestError(models.VERIFICATION_TOKEN_EXPIRED)
|
||||
}
|
||||
if verifition.Token != token {
|
||||
return httperrors.NewBadRequestError(models.VERIFICATION_TOKEN_INVALID)
|
||||
}
|
||||
// modify contact's status and verified time.
|
||||
data := jsonutils.NewDict()
|
||||
data.Set("status", jsonutils.NewString(models.CONTACT_VERIFIED))
|
||||
data.Set("verified_at", jsonutils.NewTimeString(current))
|
||||
_, err = self.Update(ctx, verifition.CID, jsonutils.JSONNull, data, nil)
|
||||
if err != nil {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyTrigger process:
|
||||
// 1.fetch contact by the information in data
|
||||
// 2.if contact'status is 'init', make a new verify and send a verify message to the contact adress
|
||||
// 3.if contact'status is 'verifying', fetch verify by CID, generate a new verify if it has expired
|
||||
// or return a error mention that "please don't try again".
|
||||
func (self *NotifyModelDispatcher) VerifyTrigger(ctx context.Context, params map[string]string, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
uid := params["<uid>"]
|
||||
contact, _ := data.GetString("contact")
|
||||
contactType, _ := data.GetString("contact_type")
|
||||
contacts, err := models.ContactManager.FetchByMore(uid, contact, contactType)
|
||||
if err != nil {
|
||||
return nil, errors.Error(fmt.Sprintf(`uid %q don't have contact %q of contact_type %q'`, uid, contact, contactType))
|
||||
}
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
scontact := contacts[0]
|
||||
|
||||
makeNewVerify := func() (jsonutils.JSONObject, error) {
|
||||
verification := models.NewSVerify(contactType, scontact.ID)
|
||||
err = models.VerifyManager.Create(ctx, userCred, verification)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
// update contact state
|
||||
updateDate := jsonutils.NewDict()
|
||||
updateDate.Set("status", jsonutils.NewString(models.CONTACT_VERIFYING))
|
||||
err = UpdateItem(models.ContactManager, &scontact, ctx, userCred, jsonutils.JSONNull, updateDate)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
processID := verification.ID
|
||||
go models.SendVerifyMessage(processID, uid, contactType, contact, verification.Token)
|
||||
ret := map[string]map[string]string{
|
||||
"contact": {
|
||||
"process_id": processID,
|
||||
},
|
||||
}
|
||||
return jsonutils.Marshal(ret), nil
|
||||
}
|
||||
if scontact.Status == models.CONTACT_INIT {
|
||||
return makeNewVerify()
|
||||
}
|
||||
if scontact.Status == models.CONTACT_VERIFYING {
|
||||
if err != nil {
|
||||
return nil, errors.Error(fmt.Sprintf(`uid %q don't have contact %q of contact_type %q`, uid, contact, contactType))
|
||||
}
|
||||
verifications, err := models.VerifyManager.FetchByCID(scontact.ID)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
current := time.Now()
|
||||
for _, verification := range verifications {
|
||||
if current.After(verification.ExpireAt) {
|
||||
//delete old one
|
||||
err = DeleteItem(models.VerifyManager, &verification, ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
return makeNewVerify()
|
||||
}
|
||||
}
|
||||
return nil, httperrors.NewGeneralError(models.ErrVeritying)
|
||||
}
|
||||
|
||||
return jsonutils.JSONNull, nil
|
||||
}
|
||||
|
||||
// DeleteContacts delete a group of contacts
|
||||
func (self *NotifyModelDispatcher) DeleteContacts(ctx context.Context, uids2 []jsonutils.JSONObject) error {
|
||||
// Get all id of uid
|
||||
uids := make([]string, len(uids2))
|
||||
for i := range uids2 {
|
||||
uids[i] = strings.Trim(uids2[i].String(), `"`)
|
||||
}
|
||||
|
||||
contacts, err := models.ContactManager.FetchByUIDs(uids)
|
||||
if err != nil {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
deleteFailed := make([]string, 0, 1)
|
||||
for _, contact := range contacts {
|
||||
err = DeleteItem(models.ContactManager, &contact, ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
deleteFailed = append(deleteFailed, contact.ID)
|
||||
}
|
||||
}
|
||||
if len(deleteFailed) != 0 {
|
||||
errInfo := strings.Join(deleteFailed, ", ") + " ; these contact delete failed."
|
||||
return errors.Error(errInfo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContacts analysis the data, update corresponding contacts if they exist in the database or create new ones.
|
||||
func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr string, query jsonutils.JSONObject, data jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) (jsonutils.JSONObject, error) {
|
||||
datas, err := data.GetArray("contacts")
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(errors.Wrapf(err, `"contacts" not found`))
|
||||
}
|
||||
type pair struct {
|
||||
contact string
|
||||
enabled string
|
||||
}
|
||||
|
||||
// contactInfos will be used to find all contact info need to update.
|
||||
// And others will be created.
|
||||
contactInfos := make(map[string]pair)
|
||||
contactTypes := make([]string, len(datas))
|
||||
for i := range datas {
|
||||
contactType, _ := datas[i].GetString("contact_type")
|
||||
contact, _ := datas[i].GetString("contact")
|
||||
enabled := "-1"
|
||||
if datas[i].Contains("enabled") {
|
||||
enabled, _ = datas[i].GetString("enabled")
|
||||
}
|
||||
contactInfos[contactType] = pair{contact, enabled}
|
||||
contactTypes[i] = contactType
|
||||
}
|
||||
|
||||
records, err := models.ContactManager.FetchByUIDAndCType(idstr, contactTypes)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
// updateFailed record the information of failed update
|
||||
updateFailed := make([]string, 0, 1)
|
||||
deleteFailed := make([]string, 0, 1)
|
||||
// UpdateItem contact info
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
for i := range records {
|
||||
contactType := records[i].ContactType
|
||||
pairUpdate := contactInfos[contactType]
|
||||
if len(pairUpdate.contact) == 0 {
|
||||
// delete
|
||||
err = DeleteItem(models.ContactManager, &records[i], ctx, userCred, jsonutils.JSONNull, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
deleteFailed = append(deleteFailed, fmt.Sprintf(`uid:%q, contact_type:%q`, idstr, contactType))
|
||||
}
|
||||
continue
|
||||
}
|
||||
updateData := jsonutils.NewDict()
|
||||
updateData.Set("contact", jsonutils.NewString(pairUpdate.contact))
|
||||
if pairUpdate.enabled != "-1" {
|
||||
updateData.Set("enabled", jsonutils.NewString(pairUpdate.enabled))
|
||||
}
|
||||
updateData.Set("status", jsonutils.NewString("init"))
|
||||
err = UpdateItem(models.ContactManager, &records[i], ctx, userCred, jsonutils.JSONNull, updateData)
|
||||
if err != nil {
|
||||
updateFailed = append(updateFailed, fmt.Sprintf(`uid:%q, contact_type:%q, contact:%q`, idstr, contactType, pairUpdate.contact))
|
||||
}
|
||||
delete(contactInfos, contactType)
|
||||
}
|
||||
|
||||
// createFailed record the information of failed creation
|
||||
createFailed := make([]string, 0, 1)
|
||||
// Create contact info
|
||||
newDatas := make([]map[string]interface{}, 0, len(contactInfos))
|
||||
for conType, conPair := range contactInfos {
|
||||
tmpMap := map[string]interface{}{
|
||||
"uid": idstr,
|
||||
"contact_type": conType,
|
||||
"contact": conPair.contact,
|
||||
}
|
||||
if conPair.enabled != "-1" {
|
||||
tmpMap["enabled"] = conPair.enabled
|
||||
}
|
||||
// dingtalk don't need verify, judge and specified status for now
|
||||
if conType == "dingtalk" {
|
||||
tmpMap["status"] = models.CONTACT_VERIFIED
|
||||
tmpMap["verified_at"] = time.Now()
|
||||
}
|
||||
newDatas = append(newDatas, tmpMap)
|
||||
}
|
||||
|
||||
for _, newData := range newDatas {
|
||||
_, err := self.Create(ctx, jsonutils.JSONNull, jsonutils.Marshal(newData), ctxIds)
|
||||
if err != nil {
|
||||
createFailed = append(createFailed, fmt.Sprintf(`uid:%q, contact:%q, contact_type:%q`, idstr, newData["contact_type"], newData["contact"]))
|
||||
}
|
||||
}
|
||||
|
||||
// generate error through updateFailed and createFailed
|
||||
if len(updateFailed) != 0 || len(createFailed) != 0 || len(deleteFailed) != 0 {
|
||||
var errInfoBuffer bytes.Buffer
|
||||
if len(updateFailed) != 0 {
|
||||
errInfoBuffer.WriteString(strings.Join(updateFailed, "; "))
|
||||
errInfoBuffer.WriteString(" update failed. ")
|
||||
}
|
||||
if len(deleteFailed) != 0 {
|
||||
errInfoBuffer.WriteString(strings.Join(updateFailed, "; "))
|
||||
errInfoBuffer.WriteString(" delete failed. ")
|
||||
}
|
||||
if len(createFailed) != 0 {
|
||||
errInfoBuffer.WriteString(strings.Join(createFailed, "; "))
|
||||
errInfoBuffer.WriteString(" create failed. ")
|
||||
}
|
||||
errInfo := errInfoBuffer.String()
|
||||
return nil, httperrors.NewGeneralError(errors.Error(errInfo))
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// fetchEnv fetch handler, params, query and body from ctx(context.Context)
|
||||
func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*NotifyModelDispatcher, map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) {
|
||||
params, query, body := appsrv.FetchEnv(ctx, w, r)
|
||||
metadata := appctx.AppContextMetadata(ctx)
|
||||
manager, ok := metadata["manager"].(*NotifyModelDispatcher)
|
||||
if !ok {
|
||||
log.Fatalf("No manager found for URL: %s", r.URL)
|
||||
}
|
||||
return manager, params, query, body
|
||||
}
|
||||
|
||||
func mergeQueryParams(params map[string]string, query jsonutils.JSONObject, excludes ...string) jsonutils.JSONObject {
|
||||
if query == nil {
|
||||
query = jsonutils.NewDict()
|
||||
}
|
||||
queryDict := query.(*jsonutils.JSONDict)
|
||||
for k, v := range params {
|
||||
if !utils.IsInStringArray(k, excludes) {
|
||||
queryDict.Add(jsonutils.NewString(v), k[1:len(k)-1])
|
||||
}
|
||||
}
|
||||
return queryDict
|
||||
}
|
||||
|
||||
// DeleteItem delete a database record corresponding to model
|
||||
func DeleteItem(manager db.IModelManager, model db.IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
err := model.ValidateDeleteCondition(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("validate delete condition error: %s", err)
|
||||
return err
|
||||
}
|
||||
err = model.CustomizeDelete(ctx, userCred, query, data)
|
||||
if err != nil {
|
||||
log.Errorf("customize delete error: %s", err)
|
||||
return httperrors.NewNotAcceptableError(err.Error())
|
||||
}
|
||||
model.PreDelete(ctx, userCred)
|
||||
err = model.Delete(ctx, userCred)
|
||||
if err != nil {
|
||||
log.Errorf("Delete error %s", err)
|
||||
return err
|
||||
}
|
||||
model.PostDelete(ctx, userCred)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateItem update a database record corresponding to model whose update fields are in data
|
||||
func UpdateItem(manager db.IModelManager, item db.IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
var err error
|
||||
|
||||
err = item.ValidateUpdateCondition(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("validate update condition error: %s", err)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
dataDict, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return httperrors.NewInternalServerError("Invalid data JSONObject")
|
||||
}
|
||||
|
||||
dataDict, err = item.ValidateUpdateData(ctx, userCred, query, dataDict)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("validate update data error: %s", err)
|
||||
log.Errorf(errMsg)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
item.PreUpdate(ctx, userCred, query, dataDict)
|
||||
|
||||
diff, err := db.Update(item, func() error {
|
||||
filterData := dataDict.CopyIncludes(updateFields(manager, userCred)...)
|
||||
err = filterData.Unmarshal(item)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("unmarshal fail: %s", err)
|
||||
log.Errorf(errMsg)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("save update error: %s", err)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
db.OpsLog.LogEvent(item, db.ACT_UPDATE, diff, userCred)
|
||||
|
||||
item.PostUpdate(ctx, userCred, query, data)
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// get the field of model which is d
|
||||
func updateFields(manager db.IModelManager, userCred mcclient.TokenCredential) []string {
|
||||
ret := make([]string, 0)
|
||||
for _, col := range manager.TableSpec().Columns() {
|
||||
tags := col.Tags()
|
||||
update := tags["update"]
|
||||
if allowAction(manager, userCred, update, db.IsAllowUpdate) {
|
||||
ret = append(ret, col.Name())
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func allowAction(manager db.IResource, userCred mcclient.TokenCredential, action string, testfunc func(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, manager db.IResource) bool) bool {
|
||||
if action == "user" {
|
||||
return true
|
||||
}
|
||||
if action == "domain" && (testfunc(rbacutils.ScopeDomain, userCred, manager) || testfunc(rbacutils.ScopeSystem, userCred, manager)) {
|
||||
return true
|
||||
}
|
||||
if action == "admin" && testfunc(rbacutils.ScopeSystem, userCred, manager) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
349
pkg/notify/handlers.go
Normal file
349
pkg/notify/handlers.go
Normal file
@@ -0,0 +1,349 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
func InitHandlers(app *appsrv.Application) {
|
||||
db.RegisterModelManager(models.ContactManager)
|
||||
db.RegisterModelManager(models.VerifyManager)
|
||||
db.RegisterModelManager(models.NotificationManager)
|
||||
db.RegisterModelManager(models.ConfigManager)
|
||||
AddNotifyDispatcher("/api/v1/", app)
|
||||
}
|
||||
|
||||
func AddNotifyDispatcher(prefix string, app *appsrv.Application) {
|
||||
var metadata map[string]interface{}
|
||||
var tags map[string]string
|
||||
|
||||
// Contact Handler
|
||||
modelDispatcher := NewNotifyModelDispatcher(models.ContactManager)
|
||||
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
|
||||
h := app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<uid>/update-contact", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(contactUpdateHandler), metadata, "contact_update", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
// List
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(listManyHandler), metadata, "list_contacts", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<uid>", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(listOneHandler), metadata, "list_by_uid", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/delete-contact", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(deleteContactHandler), metadata, "delete", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
// verify-trigger
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<uid>/verify", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(verifyTriggerHandler), metadata, "verify_trigger", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
// Verify Handler, this modelDispatcher need db.DBModelDispatcher'Create function to create Contact so this modelDispatcher is
|
||||
// NotifyModelDispatcher whose DBModelDispatcher has modelManager models.ContactManager
|
||||
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": models.VerifyManager.KeywordPlural()}
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<id>", prefix, models.VerifyManager.KeywordPlural()),
|
||||
modelDispatcher.Filter(verifyHandler), metadata, "verify", tags)
|
||||
|
||||
// notification Handler
|
||||
modelDispatcher = NewNotifyModelDispatcher(models.NotificationManager)
|
||||
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(notificationHandler), metadata, "send_notifications", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(listHandler), metadata, "send_notifications", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<id>", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(listHandler), metadata, "list_notification_by_id", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
// config Handler
|
||||
modelDispatcher = NewNotifyModelDispatcher(models.ConfigManager)
|
||||
metadata, tags = map[string]interface{}{"manager": modelDispatcher}, map[string]string{"resource": modelDispatcher.KeywordPlural()}
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(configUpdateHandler), metadata, "update_configs", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(configGetHandler), metadata, "get_configs", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, modelDispatcher.KeywordPlural()),
|
||||
modelDispatcher.Filter(configDeleteHandler), metadata, "delete_configs", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
// email handler for being compatible
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/", prefix, EMAIL_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(emailConfigUpdateHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, EMAIL_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(emailConfigGetHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, EMAIL_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(emailConfigDeleteHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("PUT",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, EMAIL_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(emailConfigUpdateHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/", prefix, SMS_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(smsConfigUpdateHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, SMS_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(smsConfigGetHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, SMS_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(smsConfigDeleteHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
h = app.AddHandler2("PUT",
|
||||
fmt.Sprintf("%s/%s/<type>", prefix, SMS_KEYWORDPLURAL),
|
||||
modelDispatcher.Filter(smsConfigUpdateHandler), metadata, "", tags)
|
||||
modelDispatcher.CustomizeHandlerInfo(h)
|
||||
|
||||
}
|
||||
|
||||
func configDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, _, _ := fetchEnv(ctx, w, r)
|
||||
err := manager.DeleteConfig(ctx, params)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func configGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
ret, err := manager.GetConfig(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
func configUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
if body.Contains("config") {
|
||||
body, _ = body.Get("config")
|
||||
}
|
||||
if body.Contains("configs") {
|
||||
body, _ = body.Get("config")
|
||||
}
|
||||
err := manager.UpdateConfig(ctx, body)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func notificationHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
data, err := body.Get(manager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.BadRequestError(w, "request body should contain %s", manager.Keyword())
|
||||
}
|
||||
ret, err := manager.CreateNotification(ctx, data)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
// verify handler
|
||||
func verifyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
err := manager.Verify(ctx, params, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
// contact update handler
|
||||
func contactUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, _, body := fetchEnv(ctx, w, r)
|
||||
|
||||
var data jsonutils.JSONObject
|
||||
if body != nil {
|
||||
if body.Contains(manager.Keyword()) {
|
||||
data, _ = body.Get(manager.Keyword())
|
||||
if data == nil {
|
||||
data = body.(*jsonutils.JSONDict)
|
||||
}
|
||||
} else {
|
||||
data = body
|
||||
}
|
||||
} else {
|
||||
data = jsonutils.NewDict()
|
||||
}
|
||||
|
||||
// check that if the uid is exist
|
||||
uid := params["<uid>"]
|
||||
_, err := utils.GetUserByID(uid)
|
||||
if err != nil {
|
||||
log.Errorf(`uid %q not found`, uid)
|
||||
httperrors.NotFoundError(w, "Uid Not Found")
|
||||
return
|
||||
}
|
||||
_, err = manager.UpdateContacts(ctx, uid, jsonutils.JSONNull, data, nil)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
httperrors.BadRequestError(w, "")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// delete contact handler
|
||||
func deleteContactHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, _, _, body := fetchEnv(ctx, w, r)
|
||||
|
||||
var data []jsonutils.JSONObject
|
||||
var err error
|
||||
if body != nil {
|
||||
data, err = body.GetArray(manager.KeywordPlural())
|
||||
if err != nil {
|
||||
httperrors.BadRequestError(w, "request body should have %s", manager.KeywordPlural())
|
||||
}
|
||||
}
|
||||
err = manager.DeleteContacts(ctx, data)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("delete contact of %s failed, error: %s", data, err)
|
||||
httperrors.GeneralServerError(w, errors.Error("delete failed"))
|
||||
}
|
||||
}
|
||||
|
||||
// verify trigger handler
|
||||
func verifyTriggerHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, _, body := fetchEnv(ctx, w, r)
|
||||
data, err := body.Get(models.ContactManager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.BadRequestError(w, "request body should have %s", manager.KeywordPlural())
|
||||
}
|
||||
ret, err := manager.VerifyTrigger(ctx, params, data)
|
||||
if err != nil {
|
||||
log.Errorf("verifyTrigger failed beacause %s", err)
|
||||
httperrors.GeneralServerError(w, err)
|
||||
}
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
//speciallist hander for contact records
|
||||
func listManyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
listResult = arrangeList(listResult)
|
||||
appsrv.SendJSON(w, modules.ListResult2JSONWithKey(listResult, manager.KeywordPlural()))
|
||||
}
|
||||
|
||||
// list handler for all resource in notify module
|
||||
func listHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, modules.ListResult2JSONWithKey(listResult, manager.KeywordPlural()))
|
||||
}
|
||||
|
||||
func listOneHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
listResult, err := manager.List(ctx, mergeQueryParams(params, query), nil)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, wrap(arrangeOne(listResult), manager.Keyword()))
|
||||
}
|
||||
|
||||
func wrap(data jsonutils.JSONObject, key string) jsonutils.JSONObject {
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(data, key)
|
||||
return ret
|
||||
}
|
||||
|
||||
// For limit option, there is a bug but don't fix it for now.
|
||||
// This limit point to contact record, but these contact records whose uid are same
|
||||
// are considered as one record.
|
||||
func arrangeList(listResult *modules.ListResult) *modules.ListResult {
|
||||
ret := make(map[string]*jsonutils.JSONArray)
|
||||
for _, data := range listResult.Data {
|
||||
uid, _ := data.GetString("uid")
|
||||
_, ok := ret[uid]
|
||||
if !ok {
|
||||
ret[uid] = jsonutils.NewArray()
|
||||
}
|
||||
ret[uid].Add(data)
|
||||
}
|
||||
data := make([]jsonutils.JSONObject, len(ret))
|
||||
index := 0
|
||||
for uid, value := range ret {
|
||||
cr := models.NewSContactResponse(uid, value.String())
|
||||
data[index] = jsonutils.Marshal(cr)
|
||||
index++
|
||||
}
|
||||
listResult.Data = data
|
||||
listResult.Total = len(ret)
|
||||
return listResult
|
||||
}
|
||||
|
||||
func arrangeOne(listResult *modules.ListResult) jsonutils.JSONObject {
|
||||
if len(listResult.Data) == 0 {
|
||||
return jsonutils.NewDict()
|
||||
}
|
||||
uid, _ := listResult.Data[0].GetString("uid")
|
||||
details := jsonutils.NewArray()
|
||||
for _, data := range listResult.Data {
|
||||
details.Add(data)
|
||||
}
|
||||
return jsonutils.Marshal(models.NewSContactResponse(uid, details.String()))
|
||||
}
|
||||
90
pkg/notify/models/base.go
Normal file
90
pkg/notify/models/base.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// 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"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SResourceBase struct {
|
||||
db.SResourceBase
|
||||
|
||||
CreateBy string `width:"128" charset:"ascii" nullable:"true" create:"optional"`
|
||||
UpdateBy string `width:"128" charset:"ascii" nullable:"true" update:"user"`
|
||||
DeleteBy string `width:"128" charset:"ascii" nullable:"true"`
|
||||
|
||||
Remark jsonutils.JSONObject `get:"user"`
|
||||
}
|
||||
|
||||
type SResourceBaseManager struct {
|
||||
db.SResourceBaseManager
|
||||
}
|
||||
|
||||
type IResourceBaseModel interface {
|
||||
db.IModel
|
||||
GetIResourceBaseModel() IResourceBaseModel
|
||||
SetDeleteBy(string)
|
||||
}
|
||||
|
||||
func NewResourceBaseManager(dt interface{}, tableName string, keyword string, keywordPlural string) SResourceBaseManager {
|
||||
return SResourceBaseManager{db.NewResourceBaseManager(dt, tableName, keyword, keywordPlural)}
|
||||
}
|
||||
|
||||
func (self *SResourceBase) GetIResourceBaseModel() IResourceBaseModel {
|
||||
return self.GetVirtualObject().(IResourceBaseModel)
|
||||
}
|
||||
|
||||
func (self *SResourceBase) SetDeleteBy(uid string) {
|
||||
self.DeleteBy = uid
|
||||
}
|
||||
|
||||
func (self *SResourceBaseManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
data, err := self.SResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Set("create_by", jsonutils.NewString(userCred.GetUserId()))
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (self *SResourceBase) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
self.SResourceBase.PreUpdate(ctx, userCred, query, data)
|
||||
self.UpdateBy = userCred.GetUserId()
|
||||
}
|
||||
|
||||
func (self *SResourceBase) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
item := self.GetIResourceBaseModel()
|
||||
_, err := db.Update(item, func() error {
|
||||
item.SetDeleteBy(userCred.GetUserId())
|
||||
return item.MarkDelete()
|
||||
})
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("save update error %s", err)
|
||||
log.Errorf(msg)
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
if userCred != nil {
|
||||
db.OpsLog.LogEvent(self, db.ACT_DELETE, self.GetShortDesc(ctx), userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
46
pkg/notify/models/consts.go
Normal file
46
pkg/notify/models/consts.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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
|
||||
|
||||
const (
|
||||
EMAIL = "email"
|
||||
MOBILE = "mobile"
|
||||
DINGTALK = "dingtalk"
|
||||
WEBCONSOLE = "webconsole"
|
||||
// Received a task about sending a notification
|
||||
NOTIFY_RECEIVED = "received"
|
||||
// Nofity module hasn't sent the notification
|
||||
NOTIFY_UNSENT = "unsent"
|
||||
// Nofity module has sent notification, but result unkown
|
||||
NOTIFY_SENT = "sent"
|
||||
// Notification was sent successfully
|
||||
NOTIFY_OK = "sent_ok"
|
||||
// That sent a notification is failed
|
||||
NOTIFY_FAIL = "sent_fail"
|
||||
// Contact's status is init which means no verifying
|
||||
CONTACT_INIT = "init"
|
||||
// Contact's status is verifying
|
||||
CONTACT_VERIFYING = "verifying"
|
||||
// Contact's status is verified
|
||||
CONTACT_VERIFIED = "verified"
|
||||
// Verification was sent
|
||||
VERIFICATION_SENT = "sent"
|
||||
// Verification was verified
|
||||
VERIFICATION_VERIFIED = "verified"
|
||||
|
||||
VERIFICATION_TOKEN_EXPIRED = "Verification code expired"
|
||||
|
||||
VERIFICATION_TOKEN_INVALID = "Incorrect verification code"
|
||||
)
|
||||
26
pkg/notify/models/errors.go
Normal file
26
pkg/notify/models/errors.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 "errors"
|
||||
|
||||
var (
|
||||
ErrContactNotFound = errors.New("Contact Not Found")
|
||||
ErrVeritying = errors.New("During the verification process, please do not try again")
|
||||
|
||||
ErrDial = errors.New("Dial Failed")
|
||||
ErrGetConfig = errors.New("Get Config Failed")
|
||||
ErrUpdateConfig = errors.New("Update Config Failed")
|
||||
)
|
||||
27
pkg/notify/models/initdb.go
Normal file
27
pkg/notify/models/initdb.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
)
|
||||
|
||||
func InitDB() error {
|
||||
for _, manager := range []db.IModelManager{
|
||||
/*
|
||||
* Important!!!
|
||||
* initialization order matters, do not change the order
|
||||
*/
|
||||
|
||||
ContactManager,
|
||||
VerifyManager,
|
||||
NotificationManager,
|
||||
ConfigManager,
|
||||
} {
|
||||
err := manager.InitializeData()
|
||||
if err != nil {
|
||||
log.Errorf("Manager %s initializeData fail %s", manager.Keyword(), err)
|
||||
// return err skip error table
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
119
pkg/notify/models/mod_config.go
Normal file
119
pkg/notify/models/mod_config.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SConfigManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ConfigManager *SConfigManager
|
||||
|
||||
func init() {
|
||||
ConfigManager = &SConfigManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SConfig{},
|
||||
"notify_t_config",
|
||||
"config",
|
||||
"configs",
|
||||
),
|
||||
}
|
||||
ConfigManager.SetVirtualObject(ConfigManager)
|
||||
}
|
||||
|
||||
// SConfig is a table which storage (k,v) and its type.
|
||||
// The three important concepts are key, value and type.
|
||||
// Key and type uniquely identify a value.
|
||||
type SConfig struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
Type string `width:"15" nullable:"false" create:"required" list:"user""`
|
||||
KeyText string `width:"30" nullable:"false" create:"required" list:"user"`
|
||||
ValueText string `width:"100" nullable:"false" create:"required" list:"user"`
|
||||
}
|
||||
|
||||
// ListItemFilter is a hook function belong to IModelManager interface when Listing.
|
||||
// This will Called in yunion.io/x/onecloud/pkg/cloudcommon/db.List function.
|
||||
func (self *SConfigManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
if !query.Contains("type") {
|
||||
return q, nil
|
||||
}
|
||||
contactType, _ := query.GetString("type")
|
||||
q.Filter(sqlchemy.Equals(q.Field("type"), contactType))
|
||||
return q, nil
|
||||
}
|
||||
|
||||
// GetValue fetch the SConfig struct corresponding to key and type.
|
||||
func (self *SConfigManager) GetValue(key, contactType string) (*SConfig, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("type"), contactType), sqlchemy.Equals(q.Field("key_text"), key)))
|
||||
configs := make([]SConfig, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &configs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Fetch SConfig by key and type failed")
|
||||
}
|
||||
if len(configs) == 0 {
|
||||
return nil, errors.Error("There is no SConfig whose key and type meet the requirement")
|
||||
}
|
||||
return &configs[0], nil
|
||||
}
|
||||
|
||||
// Get all (k, v) whose type is contactType.
|
||||
func (self *SConfigManager) GetVauleByType(contactType string) (map[string]string, error) {
|
||||
configs, err := self.GetConfigByType(contactType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make(map[string]string)
|
||||
for i := range configs {
|
||||
ret[configs[i].KeyText] = configs[i].ValueText
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *SConfigManager) InitializeData() error {
|
||||
sql := fmt.Sprintf("update %s set updated_at=gmt_modified, deleted=is_deleted, created_at=gmt_create, deleted_at=gmt_deleted, update_by=modified_by, delete_by=deleted_by", self.TableSpec().Name())
|
||||
q := sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
sql = fmt.Sprintf("update %s set type='mobile' where type='sms_aliyun'", self.TableSpec().Name())
|
||||
q = sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch all SConfig struct which type is contactType.
|
||||
func (self *SConfigManager) GetConfigByType(contactType string) ([]SConfig, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("type"), contactType))
|
||||
configs := make([]SConfig, 0, 5)
|
||||
err := db.FetchModelObjects(self, q, &configs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Fetch SConfigs by type failed")
|
||||
}
|
||||
//if len(configs) == 0 {
|
||||
// return nil, errors.Error("There is no SConfig whose type meet the requirement")
|
||||
//}
|
||||
return configs, nil
|
||||
}
|
||||
166
pkg/notify/models/mod_contact.go
Normal file
166
pkg/notify/models/mod_contact.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
type SContactManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var ContactManager *SContactManager
|
||||
|
||||
func init() {
|
||||
ContactManager = &SContactManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SContact{},
|
||||
"notify_t_contacts",
|
||||
"contact",
|
||||
"contacts",
|
||||
),
|
||||
}
|
||||
ContactManager.SetVirtualObject(ContactManager)
|
||||
}
|
||||
|
||||
type SContact struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
UID string `width:"128" nullable:"false" create:"required" list:"user" update:"user"`
|
||||
ContactType string `width:"16" nullable:"false" create:"required" list:"user" update:"user"`
|
||||
Contact string `width:"64" nullable:"false" create:"required" list:"user" update:"user"`
|
||||
Enabled string `width:"5" nullable:"false" default:"1" create:"optional" list:"user" update:"user"`
|
||||
VerifiedAt time.Time `update:"user" list:"user"`
|
||||
}
|
||||
|
||||
func (self *SContactManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowList(userCred, self)
|
||||
}
|
||||
|
||||
func (self *SContactManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SContactManager) InitializeData() error {
|
||||
sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name())
|
||||
q := sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchByUIDs(uids []string) ([]SContact, error) {
|
||||
q := self.Query()
|
||||
q = q.Filter(sqlchemy.In(q.Field("uid"), uids))
|
||||
records := make([]SContact, 0, len(uids))
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchByUIDAndCType(uid string, contactTypes []string) ([]SContact, error) {
|
||||
q := self.Query("id", "uid", "contact_type", "contact", "enabled")
|
||||
q = q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("uid"), uid), sqlchemy.In(q.Field("contact_type"), contactTypes)))
|
||||
records := make([]SContact, 0, len(contactTypes))
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchByMore(uid, contact, contactType string) ([]SContact, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("uid"), uid), sqlchemy.Equals(q.Field("contact"), contact), sqlchemy.Equals(q.Field("contact_type"), contactType)))
|
||||
records := make([]SContact, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) FetchDingtalkContacts(uid string) {
|
||||
// todo
|
||||
}
|
||||
|
||||
func (self *SContactManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
queryDict := query.(*jsonutils.JSONDict)
|
||||
if queryDict.Contains("uid") {
|
||||
uid, _ := queryDict.GetString("uid")
|
||||
q = q.Filter(sqlchemy.Equals(q.Field("uid"), uid))
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (self *SContactManager) GetAllNotify(id, contactType string, group bool) ([]SContact, error) {
|
||||
var uids []string
|
||||
var err error
|
||||
|
||||
q := self.Query()
|
||||
if !group {
|
||||
q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("uid"), id), sqlchemy.Equals(q.Field("contact_type"), contactType), sqlchemy.Equals(q.Field("status"), CONTACT_VERIFIED)))
|
||||
uids = []string{id}
|
||||
} else {
|
||||
uids, err = utils.GetUsersByGroupID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Filter(sqlchemy.AND(sqlchemy.In(q.Field("uid"), uids), sqlchemy.Equals(q.Field("contact_type"), contactType)))
|
||||
}
|
||||
if contactType == WEBCONSOLE {
|
||||
ret := make([]SContact, len(uids))
|
||||
for i := range uids {
|
||||
ret[i] = SContact{
|
||||
UID: uids[i],
|
||||
ContactType: WEBCONSOLE,
|
||||
Contact: uids[i],
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
contacts := make([]SContact, 0, 2)
|
||||
err = db.FetchModelObjects(self, q, &contacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
type SContactResponse struct {
|
||||
Id string
|
||||
Name string
|
||||
Details string
|
||||
}
|
||||
|
||||
func NewSContactResponse(uid string, details string) SContactResponse {
|
||||
name, _ := utils.GetUsernameByID(uid)
|
||||
return SContactResponse{
|
||||
Id: uid,
|
||||
Name: name,
|
||||
Details: details,
|
||||
}
|
||||
}
|
||||
247
pkg/notify/models/mod_notification.go
Normal file
247
pkg/notify/models/mod_notification.go
Normal file
@@ -0,0 +1,247 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
type SNotificationManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var NotificationManager *SNotificationManager
|
||||
|
||||
func init() {
|
||||
NotificationManager = &SNotificationManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SNotification{},
|
||||
"notify_t_notification",
|
||||
"notification",
|
||||
"notifications",
|
||||
),
|
||||
}
|
||||
NotificationManager.SetVirtualObject(NotificationManager)
|
||||
}
|
||||
|
||||
type SNotification struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
UID string `width:"128" nullable:"false" create:"required"`
|
||||
ContactType string `width:"16" nullable:"false" create:"required"`
|
||||
Topic string `width:"128" nullable:"false" create:"optional"`
|
||||
Priority string `width:"16" nullable:"false" create:"optional"`
|
||||
Msg string `create:"required"`
|
||||
ReceivedAt time.Time `nullable:"false"`
|
||||
SendAt time.Time `nullable:"false"`
|
||||
SendBy string `width:"128" nullable:"false"`
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowList(userCred, self)
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowCreate(userCred, self)
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) InitializeData() error {
|
||||
sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name())
|
||||
q := sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) BatchCreate(ctx context.Context, data jsonutils.JSONObject, contacts []SContact) ([]string, error) {
|
||||
userCred := policy.FetchUserCredential(ctx)
|
||||
ownerID, err := utils.FetchOwnerId(ctx, NotificationManager, userCred, jsonutils.JSONNull)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
msg, _ := data.GetString("msg")
|
||||
priority, _ := data.GetString("priority")
|
||||
topic, _ := data.GetString("topic")
|
||||
createFailed, createSuccess, contactSuccess := make([]string, 0), make([]*SNotification, 0, len(contacts)/2), make([]string, 0, len(contacts)/2)
|
||||
|
||||
for i := range contacts {
|
||||
createData := map[string]string{
|
||||
"uid": contacts[i].ID,
|
||||
"contact_type": contacts[i].ContactType,
|
||||
"topic": topic,
|
||||
"priority": priority,
|
||||
"msg": msg,
|
||||
"send_by": userCred.GetUserId(),
|
||||
"status": NOTIFY_UNSENT,
|
||||
}
|
||||
model, err := db.DoCreate(self, ctx, userCred, jsonutils.JSONNull, jsonutils.Marshal(createData), ownerID)
|
||||
if err != nil {
|
||||
createFailed = append(createFailed, contacts[i].ID)
|
||||
} else {
|
||||
createSuccess = append(createSuccess, model.(*SNotification))
|
||||
contactSuccess = append(contactSuccess, contacts[i].Contact)
|
||||
}
|
||||
}
|
||||
go send(createSuccess, userCred, contactSuccess)
|
||||
if len(createFailed) != 0 {
|
||||
errInfo := new(bytes.Buffer)
|
||||
errInfo.WriteString("notifications whose uid are ")
|
||||
for i := range createFailed {
|
||||
errInfo.WriteString(createFailed[i])
|
||||
errInfo.WriteString(", ")
|
||||
}
|
||||
errInfo.Truncate(errInfo.Len() - 2)
|
||||
errInfo.WriteString("created failed.")
|
||||
log.Errorf(errInfo.String())
|
||||
return nil, errors.Error("Not all notifications were sent successfully")
|
||||
}
|
||||
notificationIDs := make([]string, len(createSuccess))
|
||||
for i := range createSuccess {
|
||||
notificationIDs[i] = createSuccess[i].ID
|
||||
}
|
||||
return notificationIDs, nil
|
||||
}
|
||||
|
||||
func (self *SNotificationManager) FetchNotOK(lastTime time.Time) ([]SNotification, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.AND(sqlchemy.GE(q.Field("created_at"), lastTime), sqlchemy.NotEquals(q.Field("status"), NOTIFY_UNSENT)))
|
||||
records := make([]SNotification, 0, 10)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func send(notifications []*SNotification, userCred mcclient.TokenCredential, contacts []string) {
|
||||
var wg sync.WaitGroup
|
||||
sendone := func(notification *SNotification, contact string) {
|
||||
err := notification.SetSentAndTime(userCred)
|
||||
if err != nil {
|
||||
log.Errorf("Change notification's status failed.")
|
||||
return
|
||||
}
|
||||
err = RpcService.Send(notification.ContactType, contact, notification.Topic, notification.Msg, notification.Priority)
|
||||
if err != nil {
|
||||
log.Errorf("Send notification failed because that %s.", err.Error())
|
||||
notification.SetStatus(userCred, NOTIFY_FAIL, err.Error())
|
||||
} else {
|
||||
notification.SetStatus(userCred, NOTIFY_OK, "")
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
for i := range notifications {
|
||||
wg.Add(1)
|
||||
go sendone(notifications[i], contacts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (self *SNotification) SetSentAndTime(userCred mcclient.TokenCredential) error {
|
||||
status := NOTIFY_SENT
|
||||
if self.Status == status {
|
||||
return nil
|
||||
}
|
||||
oldStatus := self.Status
|
||||
_, err := db.Update(self, func() error {
|
||||
self.Status = status
|
||||
self.SendAt = time.Now()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reason := "sent notification"
|
||||
if userCred != nil {
|
||||
notes := fmt.Sprintf("%s=>%s", oldStatus, status)
|
||||
if len(reason) > 0 {
|
||||
notes = fmt.Sprintf("%s: %s", notes, reason)
|
||||
}
|
||||
db.OpsLog.LogEvent(self, db.ACT_UPDATE_STATUS, notes, userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SNotification) SetStatusWithoutUserCred(status string) error {
|
||||
_, err := db.Update(self, func() error {
|
||||
self.Status = status
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func sendWithoutUserCred(notifications []SNotification) {
|
||||
var wg sync.WaitGroup
|
||||
sendone := func(notification SNotification) {
|
||||
// Get contact
|
||||
contact, err := ContactManager.FetchByUIDAndCType(notification.UID, []string{notification.ContactType})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(contact) == 0 {
|
||||
return
|
||||
}
|
||||
// sent_at update todo
|
||||
notification.SetStatusWithoutUserCred(NOTIFY_SENT)
|
||||
err = RpcService.Send(notification.ContactType, contact[0].Contact, notification.Topic, notification.Msg, notification.Priority)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Send notification failed because that %s.", err.Error())
|
||||
notification.SetStatusWithoutUserCred(NOTIFY_FAIL)
|
||||
} else {
|
||||
notification.SetStatusWithoutUserCred(NOTIFY_OK)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
for i := range notifications {
|
||||
wg.Add(1)
|
||||
go sendone(notifications[i])
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func ReSend(minutes int) {
|
||||
scope := time.Duration(minutes) * time.Minute
|
||||
for {
|
||||
select {
|
||||
case <-time.After(scope / 2):
|
||||
//lastTime := time.Now().Add(-scope)
|
||||
//q := NotificationManager.Query()
|
||||
notifications, err := NotificationManager.FetchNotOK(time.Now().Add(-scope))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
sendWithoutUserCred(notifications)
|
||||
}
|
||||
}
|
||||
}
|
||||
153
pkg/notify/models/mod_verify.go
Normal file
153
pkg/notify/models/mod_verify.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// 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"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
type SVerifyManager struct {
|
||||
SStatusStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var VerifyManager *SVerifyManager
|
||||
|
||||
func init() {
|
||||
VerifyManager = &SVerifyManager{
|
||||
SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(
|
||||
SVerify{},
|
||||
"notify_t_verify",
|
||||
"verification",
|
||||
"verifications",
|
||||
),
|
||||
}
|
||||
VerifyManager.SetVirtualObject(VerifyManager)
|
||||
}
|
||||
|
||||
type SVerify struct {
|
||||
SStatusStandaloneResourceBase
|
||||
|
||||
CID string `width:"128" nullable:"false" create:"required" list:"user"`
|
||||
Token string `width:"200" nullable:"false" create:"required" list:"user"`
|
||||
SendAt time.Time `nullable:"false" create:"optional"`
|
||||
ExpireAt time.Time `create:"required" list:"user"`
|
||||
}
|
||||
|
||||
// NewSVerify Generate a SVerify instance which implement a Verification Token.
|
||||
func NewSVerify(contactType string, cid string) *SVerify {
|
||||
var token string
|
||||
var expireAt time.Time
|
||||
now := time.Now()
|
||||
if contactType == EMAIL {
|
||||
token = utils.GenerateEmailToken(32)
|
||||
expireAt = now.Add(12 * time.Hour)
|
||||
} else {
|
||||
token = utils.GenerateMobileToken()
|
||||
expireAt = now.Add(5 * time.Minute)
|
||||
}
|
||||
ret := &SVerify{
|
||||
CID: cid,
|
||||
Token: token,
|
||||
ExpireAt: expireAt,
|
||||
SendAt: now,
|
||||
}
|
||||
ret.ID = DefaultUUIDGenerator()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) InitializeData() error {
|
||||
sql := fmt.Sprintf("update %s set updated_at=update_at, deleted=is_deleted", self.TableSpec().Name())
|
||||
q := sqlchemy.NewRawQuery(sql, "")
|
||||
q.Row()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) FetchByCID(cid string) ([]SVerify, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("cid"), cid))
|
||||
records := make([]SVerify, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) FetchByID(id string) ([]SVerify, error) {
|
||||
q := self.Query()
|
||||
q.Filter(sqlchemy.Equals(q.Field("id"), id))
|
||||
records := make([]SVerify, 0, 1)
|
||||
err := db.FetchModelObjects(self, q, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (self *SVerifyManager) Create(ctx context.Context, userCred mcclient.TokenCredential, verify *SVerify) error {
|
||||
data := jsonutils.Marshal(verify)
|
||||
ownerID, err := utils.FetchOwnerId(ctx, self, userCred, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.DoCreate(self, ctx, userCred, jsonutils.JSONNull, data, ownerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendVerifyMessage(processId, uid, contactType, contact, token string) {
|
||||
var err error
|
||||
var msg string
|
||||
if contactType == "email" {
|
||||
emailUrl := strings.Replace(options.Options.VerifyEmailUrl, "{0}", processId, 1)
|
||||
emailUrl = strings.Replace(emailUrl, "{1}", token, 1)
|
||||
|
||||
// get uName
|
||||
uName, err := utils.GetUsernameByID(uid)
|
||||
if err != nil || len(uName) == 0 {
|
||||
uName = "用户"
|
||||
}
|
||||
data := struct {
|
||||
Name string
|
||||
Link string
|
||||
}{uName, emailUrl}
|
||||
jsonStr, _ := json.Marshal(data)
|
||||
msg = string(jsonStr)
|
||||
} else if contactType == "mobile" {
|
||||
msg = fmt.Sprintf(`{"code": "%s"}`, token)
|
||||
} else {
|
||||
//todo
|
||||
}
|
||||
|
||||
err = RpcService.Send(contactType, contact, "verify", msg, "")
|
||||
if err != nil {
|
||||
log.Errorf("Send verify message failed because that %s.", err.Error())
|
||||
}
|
||||
}
|
||||
269
pkg/notify/models/send.go
Normal file
269
pkg/notify/models/send.go
Normal file
@@ -0,0 +1,269 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrSendServiceNotFound means SRpcService's SendSerivces hasn't this Send Service.
|
||||
ErrSendServiceNotFound = errors.Error("Send Service Not Found")
|
||||
NOTINIT = "Send service hasn't been init"
|
||||
)
|
||||
|
||||
// RpcService is a single case of SRpcService
|
||||
var RpcService *SRpcService
|
||||
|
||||
// SRpcService provide rpc service about sending message for notify module and manage these services.
|
||||
// SendServices storage all send service and its name.
|
||||
// lock protect the SendServices.
|
||||
type SRpcService struct {
|
||||
SendServices map[string]*rpc.Client
|
||||
socketFileDir string
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewSRpcService create a SRpcService
|
||||
func NewSRpcService(socketFileDir string) *SRpcService {
|
||||
return &SRpcService{
|
||||
SendServices: make(map[string]*rpc.Client),
|
||||
socketFileDir: socketFileDir,
|
||||
}
|
||||
}
|
||||
|
||||
// InitAll init all Send Services, the init process is that:
|
||||
// find all socket file in directory 'self.socketFileDir', if wrong return error;
|
||||
// the name of file is the service's name; then try to dial to this rpc service
|
||||
// through corresponding socket file, if failed, only print log but not return error.
|
||||
func (self *SRpcService) InitAll() error {
|
||||
files, err := ioutil.ReadDir(self.socketFileDir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read dir %s failed", self.socketFileDir)
|
||||
}
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !file.IsDir() && strings.Contains(filename, ".sock") {
|
||||
serviceName := filename[:len(filename)-5]
|
||||
self.checkAndAddOne(serviceName)
|
||||
}
|
||||
}
|
||||
if len(self.SendServices) == 0 {
|
||||
log.Errorf("No available send service.")
|
||||
} else {
|
||||
log.Infof("Total %d send service init successful", len(self.SendServices))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateServices will detect the self.sockFileDir every delay seconds.
|
||||
// Add new service and delete disappeared one from self.SendServices.
|
||||
func (self *SRpcService) UpdateServices(delay int) {
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Duration(delay) * time.Second):
|
||||
err := self.updateService()
|
||||
if err != nil {
|
||||
log.Errorf("update services failed because that %s.", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StopAll stop all send service in self.SenderServices normally which can delete the socket file.
|
||||
func (self *SRpcService) StopAll() {
|
||||
for _, service := range self.SendServices {
|
||||
service.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Send call the corresponding rpc server.Send to send messager.
|
||||
func (self *SRpcService) Send(contactType, contact, topic, msg, priority string) error {
|
||||
self.lock.RLock()
|
||||
sendService, ok := self.SendServices[contactType]
|
||||
self.lock.RUnlock()
|
||||
var err error
|
||||
if !ok {
|
||||
sendService, err = self.checkAndAddOne(contactType)
|
||||
if err == ErrDial {
|
||||
return ErrSendServiceNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Check or Add connection failed")
|
||||
}
|
||||
}
|
||||
args := SSendArgs{
|
||||
Contact: contact,
|
||||
Topic: topic,
|
||||
Message: msg,
|
||||
Priority: priority,
|
||||
}
|
||||
reply := SSendReply{}
|
||||
err = sendService.Call("Server.Send", &args, &reply)
|
||||
if err != nil {
|
||||
// should check and send again.
|
||||
// Possible situation: notify always keep connection but remote guy have restarted
|
||||
// so that connection valid.
|
||||
sendService, err = self.checkAndAddOne(contactType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Check or Add connection failed")
|
||||
}
|
||||
err = sendService.Call("Server.Send", &args, &reply)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Send message failed.")
|
||||
}
|
||||
if !reply.Success {
|
||||
return errors.Error(fmt.Sprintf("Send message failed because that %s.", reply.Msg))
|
||||
}
|
||||
}
|
||||
if !reply.Success {
|
||||
if reply.Msg != NOTINIT {
|
||||
return errors.Error(fmt.Sprintf("Send message failed because that %s.", reply.Msg))
|
||||
}
|
||||
// should check and send again
|
||||
sendService, err = self.checkAndAddOne(contactType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Check or Add connection failed")
|
||||
}
|
||||
err = sendService.Call("Server.Send", &args, &reply)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Send message failed.")
|
||||
}
|
||||
if !reply.Success {
|
||||
return errors.Error(fmt.Sprintf("Send message failed because that %s.", reply.Msg))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartService can restart remote rpc server and pass config info.
|
||||
// When first init notify Server, must Call this function.
|
||||
// When accept the request about changing config, must Call this function.
|
||||
func (self *SRpcService) RestartService(config map[string]string, serviceName string) {
|
||||
self.lock.RLock()
|
||||
sendService, ok := self.SendServices[serviceName]
|
||||
self.lock.RUnlock()
|
||||
var err error
|
||||
if !ok {
|
||||
sendService, err = self.checkAndAddOne(serviceName)
|
||||
if err != nil {
|
||||
log.Debugf("Restart Failed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
args := SRestartArgs{Config: config}
|
||||
|
||||
reply := SSendReply{}
|
||||
err = sendService.Call("Server.UpdateConfig", &args, &reply)
|
||||
if err != nil || !reply.Success {
|
||||
log.Errorf("Restart rpc serve whose name is %s failed.", serviceName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// CheckAndAddOne check the status of service 'serviceName'
|
||||
// If fail to dial to service, delete and remove sock file.
|
||||
// if dial successfully, try to restart the service.
|
||||
func (self *SRpcService) checkAndAddOne(serviceName string) (*rpc.Client, error) {
|
||||
// Try to connect again
|
||||
filename := filepath.Join(self.socketFileDir, serviceName+".sock")
|
||||
rpcService, err := rpc.Dial("unix", filename)
|
||||
if err != nil {
|
||||
log.Debugf("Try to dial to service failed which unix socket file name is %s.", filename)
|
||||
// This file maybe left behind inadvertently, so we should try to delete it
|
||||
os.Remove(filename)
|
||||
self.lock.Lock()
|
||||
delete(self.SendServices, serviceName)
|
||||
self.lock.Unlock()
|
||||
return nil, ErrDial
|
||||
}
|
||||
|
||||
// GetKeyValue to config rpc Service
|
||||
config, err := ConfigManager.GetVauleByType(serviceName)
|
||||
if err != nil {
|
||||
log.Debugf("Init service error which unix socket file name is %s because that get config about this failed", filename)
|
||||
return nil, ErrGetConfig
|
||||
}
|
||||
args := SRestartArgs{config}
|
||||
reply := SSendReply{}
|
||||
rpcService.Call("Server.UpdateConfig", &args, &reply)
|
||||
if !reply.Success {
|
||||
log.Debugf("Init service error which unix socket file name is %s because that %s.", filename, reply.Msg)
|
||||
return nil, ErrUpdateConfig
|
||||
}
|
||||
self.lock.Lock()
|
||||
self.SendServices[serviceName] = rpcService
|
||||
self.lock.Unlock()
|
||||
return rpcService, nil
|
||||
}
|
||||
|
||||
func (self *SRpcService) updateService() error {
|
||||
files, err := ioutil.ReadDir(self.socketFileDir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read dir %s failed", self.socketFileDir)
|
||||
}
|
||||
original := make(map[string]*rpc.Client)
|
||||
self.lock.RLock()
|
||||
for serviceName, client := range self.SendServices {
|
||||
original[serviceName] = client
|
||||
}
|
||||
self.lock.RUnlock()
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !file.IsDir() && strings.Contains(filename, ".sock") {
|
||||
serviceName := filename[:len(filename)-5]
|
||||
if _, ok := self.SendServices[serviceName]; ok {
|
||||
delete(original, serviceName)
|
||||
continue
|
||||
}
|
||||
self.checkAndAddOne(serviceName)
|
||||
}
|
||||
}
|
||||
self.lock.Lock()
|
||||
for serviceName := range original {
|
||||
delete(self.SendServices, serviceName)
|
||||
}
|
||||
self.lock.Unlock()
|
||||
for _, client := range original {
|
||||
client.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SSendArgs struct {
|
||||
Contact string
|
||||
Topic string
|
||||
Message string
|
||||
Priority string
|
||||
}
|
||||
|
||||
type SRestartArgs struct {
|
||||
Config map[string]string
|
||||
}
|
||||
|
||||
type SSendReply struct {
|
||||
Success bool
|
||||
Msg string
|
||||
}
|
||||
103
pkg/notify/models/standalone.go
Normal file
103
pkg/notify/models/standalone.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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 (
|
||||
"database/sql"
|
||||
|
||||
"yunion.io/x/pkg/util/stringutils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
)
|
||||
|
||||
type UUIDGenerator func() string
|
||||
|
||||
var (
|
||||
DefaultUUIDGenerator = stringutils.UUID4
|
||||
)
|
||||
|
||||
type SStandaloneResourceBase struct {
|
||||
SResourceBase
|
||||
|
||||
ID string `width:"128" charset:"ascii" primary:"true" list:"user" create:"optional"`
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) BeforeInsert() {
|
||||
if len(model.ID) == 0 {
|
||||
model.ID = DefaultUUIDGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
type SStandaloneResourceBaseManager struct {
|
||||
SResourceBaseManager
|
||||
}
|
||||
|
||||
func NewStandaloneResourceBaseManager(dt interface{}, tableName string, keyword string, keywordPlural string) SStandaloneResourceBaseManager {
|
||||
return SStandaloneResourceBaseManager{NewResourceBaseManager(dt, tableName, keyword, keywordPlural)}
|
||||
}
|
||||
|
||||
func (manager *SStandaloneResourceBaseManager) GetIStandaloneModelManager() db.IStandaloneModelManager {
|
||||
return manager.GetVirtualObject().(db.IStandaloneModelManager)
|
||||
}
|
||||
|
||||
func (manager *SStandaloneResourceBaseManager) FilterById(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery {
|
||||
return q.Equals("id", idStr)
|
||||
}
|
||||
|
||||
func (manager *SStandaloneResourceBaseManager) FilterByNotId(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery {
|
||||
return q.NotEquals("id", idStr)
|
||||
}
|
||||
|
||||
func (manager *SStandaloneResourceBaseManager) FetchById(idStr string) (db.IModel, error) {
|
||||
return FetchById(manager.GetIStandaloneModelManager(), idStr)
|
||||
}
|
||||
|
||||
func FetchById(manager db.IModelManager, idStr string) (db.IModel, error) {
|
||||
q := manager.Query()
|
||||
q = manager.FilterById(q, idStr)
|
||||
count, err := q.CountWithError()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count == 1 {
|
||||
obj, err := db.NewModelObject(manager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = q.First(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return obj, nil
|
||||
}
|
||||
} else if count > 1 {
|
||||
return nil, sqlchemy.ErrDuplicateEntry
|
||||
} else {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) StandaloneModelManager() db.IStandaloneModelManager {
|
||||
return model.GetModelManager().(db.IStandaloneModelManager)
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) GetId() string {
|
||||
return model.ID
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) GetIStandaloneModel() db.IStandaloneModel {
|
||||
return model.GetVirtualObject().(db.IStandaloneModel)
|
||||
}
|
||||
91
pkg/notify/models/statusstandalone.go
Normal file
91
pkg/notify/models/statusstandalone.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
)
|
||||
|
||||
type SStatusStandaloneResourceBase struct {
|
||||
SStandaloneResourceBase
|
||||
|
||||
Status string `width:"36" charset:"ascii" nullable:"false" default:"init" list:"user" create:"optional" update:"user"`
|
||||
}
|
||||
|
||||
type SStatusStandaloneResourceBaseManager struct {
|
||||
SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
func NewStatusStandaloneResourceBaseManager(dt interface{}, tableName string, keyword string, keywordPlural string) SStatusStandaloneResourceBaseManager {
|
||||
return SStatusStandaloneResourceBaseManager{SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager(dt, tableName, keyword, keywordPlural)}
|
||||
}
|
||||
|
||||
func (model *SStatusStandaloneResourceBase) SetStatus(userCred mcclient.TokenCredential, status string, reason string) error {
|
||||
if model.Status == status {
|
||||
return nil
|
||||
}
|
||||
oldStatus := model.Status
|
||||
_, err := db.Update(model, func() error {
|
||||
model.Status = status
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userCred != nil {
|
||||
notes := fmt.Sprintf("%s=>%s", oldStatus, status)
|
||||
if len(reason) > 0 {
|
||||
notes = fmt.Sprintf("%s: %s", notes, reason)
|
||||
}
|
||||
db.OpsLog.LogEvent(model, db.ACT_UPDATE_STATUS, notes, userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (model *SStatusStandaloneResourceBase) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAllowPerform(rbacutils.ScopeSystem, userCred, model, "status")
|
||||
}
|
||||
|
||||
func (model *SStatusStandaloneResourceBase) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
status, err := data.GetString("status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reason, _ := data.GetString("reason")
|
||||
err = model.SetStatus(userCred, status, reason)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (model *SStatusStandaloneResourceBase) IsInStatus(status ...string) bool {
|
||||
return utils.IsInStringArray(model.Status, status)
|
||||
}
|
||||
|
||||
func (model *SStatusStandaloneResourceBase) AllowGetDetailsStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return db.IsAllowGetSpec(rbacutils.ScopeSystem, userCred, model, "status")
|
||||
}
|
||||
|
||||
func (model *SStatusStandaloneResourceBase) GetDetailsStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Add(jsonutils.NewString(model.Status), "status")
|
||||
return ret, nil
|
||||
}
|
||||
32
pkg/notify/options/options.go
Normal file
32
pkg/notify/options/options.go
Normal file
@@ -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 options
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
)
|
||||
|
||||
type NotifyOption struct {
|
||||
options.CommonOptions
|
||||
options.DBOptions
|
||||
|
||||
DingtalkEnabled bool `help:"Enable dingtalk"`
|
||||
SocketFileDir string `help:"Socket file directory" default:"/etc/yunion/notify"`
|
||||
UpdateInterval int `help:"Update send services interval(unit:s)" default:30`
|
||||
VerifyEmailUrl string
|
||||
ReSendScope int `help:"Resend all messages that have not been sent successfully within ReSendScope minutes"`
|
||||
}
|
||||
|
||||
var Options NotifyOption
|
||||
66
pkg/notify/service.go
Normal file
66
pkg/notify/service.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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 (
|
||||
"os"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/app"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
"yunion.io/x/onecloud/pkg/notify/utils"
|
||||
)
|
||||
|
||||
func StartService() {
|
||||
// parse options
|
||||
opts := &options.Options
|
||||
commonOpts := &options.Options.CommonOptions
|
||||
dbOpts := &options.Options.DBOptions
|
||||
baseOpts := &options.Options.BaseOptions
|
||||
common_options.ParseOptions(opts, os.Args, "notify.conf", "notify")
|
||||
|
||||
// init auth
|
||||
app.InitAuth(commonOpts, func() {
|
||||
log.Infof("Auth complete!")
|
||||
})
|
||||
|
||||
// Session for user manager in keystone
|
||||
utils.InitSession(commonOpts)
|
||||
|
||||
// init handler
|
||||
applicaion := app.InitApp(baseOpts, true)
|
||||
InitHandlers(applicaion)
|
||||
|
||||
// init database
|
||||
db.EnsureAppInitSyncDB(applicaion, dbOpts, models.InitDB)
|
||||
defer cloudcommon.CloseDB()
|
||||
|
||||
// init rpc service
|
||||
models.RpcService = models.NewSRpcService(opts.SocketFileDir)
|
||||
models.RpcService.InitAll()
|
||||
defer models.RpcService.StopAll()
|
||||
go models.RpcService.UpdateServices(opts.UpdateInterval)
|
||||
|
||||
// start ReSend service
|
||||
go models.ReSend(opts.ReSendScope)
|
||||
|
||||
app.ServeForever(applicaion, baseOpts)
|
||||
}
|
||||
59
pkg/notify/utils/keystone.go
Normal file
59
pkg/notify/utils/keystone.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
var (
|
||||
session *mcclient.ClientSession
|
||||
)
|
||||
|
||||
func InitSession(options *options.CommonOptions) {
|
||||
session = auth.GetAdminSession(context.Background(), options.Region, "v3")
|
||||
}
|
||||
|
||||
func GetUserByID(id string) (jsonutils.JSONObject, error) {
|
||||
return modules.UsersV3.Get(session, id, jsonutils.NewDict())
|
||||
}
|
||||
|
||||
func GetUsersByGroupID(gid string) ([]string, error) {
|
||||
ret, err := modules.Groups.GetUsers(session, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make([]string, len(ret.Data))
|
||||
for i := range ret.Data {
|
||||
ids[i], _ = ret.Data[i].GetString("id")
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func GetUsernameByID(id string) (string, error) {
|
||||
user, err := GetUserByID(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name, _ := user.GetString("name")
|
||||
return name, nil
|
||||
}
|
||||
60
pkg/notify/utils/others.go
Normal file
60
pkg/notify/utils/others.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"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/rbacutils"
|
||||
)
|
||||
|
||||
func FetchOwnerId(ctx context.Context, manager db.IModelManager, userCred mcclient.TokenCredential, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
|
||||
var ownerId mcclient.IIdentityProvider
|
||||
var err error
|
||||
if manager.ResourceScope() != rbacutils.ScopeSystem {
|
||||
ownerId, err = manager.FetchOwnerId(ctx, data)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
}
|
||||
if ownerId == nil {
|
||||
ownerId = userCred
|
||||
}
|
||||
return ownerId, nil
|
||||
}
|
||||
|
||||
func GenerateMobileToken() string {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
token := fmt.Sprintf("%06v", rnd.Int31n(1000000))
|
||||
return token
|
||||
}
|
||||
|
||||
func GenerateEmailToken(tokenLen int) string {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
token := new(bytes.Buffer)
|
||||
for token.Len() < tokenLen {
|
||||
token.WriteString(fmt.Sprintf("%x", rnd.Int31()))
|
||||
}
|
||||
return token.String()[:tokenLen]
|
||||
}
|
||||
33
pkg/notify/utils/utils_test.go
Normal file
33
pkg/notify/utils/utils_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenEmailToken(t *testing.T) {
|
||||
s := GenerateEmailToken(32)
|
||||
if len(s) != 32 {
|
||||
t.Error("email token length should be 32")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenMobileToke(t *testing.T) {
|
||||
s := GenerateMobileToken()
|
||||
if len(s) != 6 {
|
||||
t.Errorf("mobile token length should be 6")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user