From 5f36cbb31c3ef0ba6178cb9362dd608053da9f84 Mon Sep 17 00:00:00 2001 From: Rain Date: Wed, 4 Mar 2020 15:44:14 +0800 Subject: [PATCH] feature(notify): Add query and return value for contact UPDATE interface 1. Add same return value with contact GET interface when updating 2. Add pull params for query to pull subcontact by mobile number. For example, pull user_id from dingtalk throught user's mobile number. 3. Add corresponding feature based on above for climc. 4. UserCache will try FetchByName if there is no id matched user when fetching users from KeyStone. --- cmd/climc/shell/contacts.go | 35 ++++++++++++++++++---------- cmd/climc/shell/notification.go | 2 +- pkg/cloudcommon/db/usercache.go | 13 +++++++---- pkg/mcclient/modules/mod_contacts.go | 7 ++++-- pkg/notify/dispatcher.go | 28 +++++++++++++++++----- pkg/notify/handlers.go | 10 ++------ pkg/notify/models/mod_contact.go | 2 ++ pkg/notify/models/worker.go | 34 +++++++++++++-------------- pkg/notify/rpc/send.go | 12 ++++++---- 9 files changed, 88 insertions(+), 55 deletions(-) diff --git a/cmd/climc/shell/contacts.go b/cmd/climc/shell/contacts.go index 2958627ad9..1f4d6ab522 100644 --- a/cmd/climc/shell/contacts.go +++ b/cmd/climc/shell/contacts.go @@ -29,11 +29,11 @@ func init() { * 操作用户的通信地址(如果用户的通信地址不存在则进行添加;如果已存在则进行修改;如果设置空则进行删除。) */ type ContactsUpdateOptions struct { - UID string `help:"The user you wanna add contact to (Keystone User ID)"` - 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"` - UpdateDingtalk bool `help:"if update dingtalk"` + UID string `help:"The user you wanna add contact to (Keystone User ID)"` + 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"` + Pull string `help:"pull some subcontacts(e.g., dingtalk, feishu, etc) related to mobile"` } R(&ContactsUpdateOptions{}, "contact-update", "Create, delete or update contact for user", func(s *mcclient.ClientSession, args *ContactsUpdateOptions) error { arr := jsonutils.NewArray() @@ -52,12 +52,8 @@ func init() { params := jsonutils.NewDict() params.Add(arr, "contacts") - if args.UpdateDingtalk { - params.Add(jsonutils.JSONTrue, "update_dingtalk") - } - - contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params) + contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", args.Pull, params) if err != nil { return err } @@ -66,6 +62,21 @@ func init() { return nil }) + type ContactsPullOptions struct { + UID string `help:"The user you wanna pull contact"` + CONTACTTYPE string `help:"The contact type"` + } + R(&ContactsPullOptions{}, "contact-pull", "Pull contact", func(s *mcclient.ClientSession, args *ContactsPullOptions) error { + params := jsonutils.NewDict() + params.Set("contacts", jsonutils.NewArray()) + contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", args.CONTACTTYPE, params) + if err != nil { + return err + } + printObject(contact) + return nil + }) + type ContactsDeleteOptions struct { UID string `help:"The user you wanna add contact to (Keystone User ID)"` CONTACTTYPE string `help:"The contact type email|mobile|dingtalk" choices:"email|mobile|dingtalk"` @@ -78,7 +89,7 @@ func init() { arr.Add(tmpObj) params := jsonutils.NewDict() params.Add(arr, "contacts") - contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params) + contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", "", params) if err != nil { return err } @@ -169,7 +180,7 @@ func init() { tmpDict := jsonutils.NewDict() tmpDict.Add(jsonutils.NewString(args.CONTACT_TYPE), "contact_type") tmpDict.Add(jsonutils.NewString(args.CONTACT), "contact") - _, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "verify", tmpDict) + _, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "verify", "", tmpDict) if err != nil { return err } diff --git a/cmd/climc/shell/notification.go b/cmd/climc/shell/notification.go index 39285ec0cb..15f375d393 100644 --- a/cmd/climc/shell/notification.go +++ b/cmd/climc/shell/notification.go @@ -29,7 +29,7 @@ func init() { */ type NotificationCreateOptions struct { - CONTACTTYPE string `help:"User's contacts type" choices:"email|mobile|dingtalk|webconsole"` + CONTACTTYPE string `help:"User's contacts type"` TOPIC string `help:"Title or topic of the notification"` PRIORITY string `help:"Priority of the notification" choices:"normal|important|fatal"` MSG string `help:"The content of the notification"` diff --git a/pkg/cloudcommon/db/usercache.go b/pkg/cloudcommon/db/usercache.go index 2a76b4587a..f3f47e0a1c 100644 --- a/pkg/cloudcommon/db/usercache.go +++ b/pkg/cloudcommon/db/usercache.go @@ -115,10 +115,15 @@ func (manager *SUserCacheManager) FetchUserFromKeystone(ctx context.Context, idS user, err := modules.UsersV3.GetById(s, idStr, nil) if err != nil { if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 { - return nil, sql.ErrNoRows + user, err = modules.UsersV3.GetByName(s, idStr, nil) + if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 { + return nil, sql.ErrNoRows + } + } + if err != nil { + log.Errorf("fetch user %s fail %s", idStr, err) + return nil, errors.Wrap(err, "modules.UsersV3.Get") } - log.Errorf("fetch user %s fail %s", idStr, err) - return nil, errors.Wrap(err, "modules.UsersV3.Get") } id, _ := user.GetString("id") name, _ := user.GetString("name") @@ -133,7 +138,7 @@ func (manager *SUserCacheManager) Save(ctx context.Context, idStr string, name s objo, err := manager.FetchById(idStr) if err != nil && err != sql.ErrNoRows { - log.Errorf("FetchTenantbyId fail %s", err) + log.Errorf("FetchUserbyId fail %s", err) return nil, err } if err == nil { diff --git a/pkg/mcclient/modules/mod_contacts.go b/pkg/mcclient/modules/mod_contacts.go index 8de03d55f8..7cadf9666e 100644 --- a/pkg/mcclient/modules/mod_contacts.go +++ b/pkg/mcclient/modules/mod_contacts.go @@ -45,7 +45,7 @@ func (this *ContactsManager) DoBatchDeleteContacts(s *mcclient.ClientSession, pa return modulebase.Post(this.ResourceManager, s, path, params, this.Keyword) } -func (this *ContactsManager) CustomizedPerformAction(session *mcclient.ClientSession, id string, action string, +func (this *ContactsManager) CustomizedPerformAction(session *mcclient.ClientSession, id, action, pull string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { body := jsonutils.NewDict() @@ -53,7 +53,10 @@ func (this *ContactsManager) CustomizedPerformAction(session *mcclient.ClientSes body.Add(params, this.Keyword) } path := fmt.Sprintf("/%s/%s/%s?uname=true", this.ContextPath(nil), url.PathEscape(id), url.PathEscape(action)) - return modulebase.Post(this.ResourceManager, session, path, params, this.KeywordPlural) + if len(pull) > 0 { + path += fmt.Sprintf("&pull=%s", pull) + } + return modulebase.Post(this.ResourceManager, session, path, body, this.KeywordPlural) } func (this *ContactsManager) CustomizedGet(session *mcclient.ClientSession, id string, diff --git a/pkg/notify/dispatcher.go b/pkg/notify/dispatcher.go index 44d7186d7a..94048db07b 100644 --- a/pkg/notify/dispatcher.go +++ b/pkg/notify/dispatcher.go @@ -379,7 +379,7 @@ func (self *NotifyModelDispatcher) DeleteContacts(ctx context.Context, uids2 []j // UpdateContacts analysis the data and update corresponding contacts if they exist in the database create new ones. func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr string, query jsonutils.JSONObject, - datas []jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) error { + datas []jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) (jsonutils.JSONObject, error) { type pair struct { contact string @@ -406,7 +406,7 @@ func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr str records, err := models.ContactManager.FetchByUIDAndCType(idstr, contactTypes) if err != nil { - return httperrors.NewGeneralError(err) + return nil, httperrors.NewGeneralError(err) } // updateFailed record the information of failed update @@ -482,13 +482,29 @@ func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr str errInfoBuffer.WriteString(" create failed. ") } errInfo := errInfoBuffer.String() - return httperrors.NewGeneralError(errors.Error(errInfo)) + return nil, httperrors.NewGeneralError(errors.Error(errInfo)) } - if query.Contains("update_dingtalk") { - models.UpdateDingtalk(idstr) + if query.Contains("pull") { + cType, _ := query.GetString("pull") + models.PullContact(idstr, cType) } - return nil + + // keep the return value same as this of the GET interface + ret := jsonutils.NewDict() + contact, err := models.ContactManager.FetchByUIDs(ctx, []string{idstr}, false) + if err != nil { + log.Errorf(err.Error()) + return ret, nil + } + outDetails, err := contact[0].GetExtraDetails(ctx, userCred, ret, false) + if err != nil { + log.Errorf(err.Error()) + return ret, nil + } + out := jsonutils.Marshal(outDetails) + out.(*jsonutils.JSONDict).Set("created_at", jsonutils.NewString(contact[0].CreatedAt.String())) + return out, nil } func (self *NotifyModelDispatcher) UpdateTemplate(ctx context.Context, ctype string, query jsonutils.JSONObject, diff --git a/pkg/notify/handlers.go b/pkg/notify/handlers.go index 95b4f4d9cc..6025d21026 100644 --- a/pkg/notify/handlers.go +++ b/pkg/notify/handlers.go @@ -306,19 +306,13 @@ func contactUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Re } uid := params[""] - queryDict := mergeQueryParams(params, query) - update, _ := body.Bool(manager.Keyword(), "update_dingtalk") - if update { - dict := queryDict.(*jsonutils.JSONDict) - dict.Add(jsonutils.JSONTrue, "update_dingtalk") - } - err = manager.UpdateContacts(ctx, uid, queryDict, data, nil) + out, err := manager.UpdateContacts(ctx, uid, mergeQueryParams(params, query), data, nil) if err != nil { log.Errorf(err.Error()) httperrors.BadRequestError(w, "") return } - return + appsrv.SendJSON(w, wrap(out, manager.Keyword())) } // delete contact handler diff --git a/pkg/notify/models/mod_contact.go b/pkg/notify/models/mod_contact.go index 9fbab37a8f..94d2345b5c 100644 --- a/pkg/notify/models/mod_contact.go +++ b/pkg/notify/models/mod_contact.go @@ -115,6 +115,8 @@ func (self *SContactManager) InitializeData() error { return nil } +// FetchByUIDs fetch all SContancts whose uid included in uids. +// If some elements of uids are uname of users, setting param 'uname' as true will fetch correct results. func (self *SContactManager) FetchByUIDs(ctx context.Context, uids []string, uname bool) ([]SContact, error) { var err error if uname { diff --git a/pkg/notify/models/worker.go b/pkg/notify/models/worker.go index 5c981f3391..8d2660633f 100644 --- a/pkg/notify/models/worker.go +++ b/pkg/notify/models/worker.go @@ -108,60 +108,60 @@ func SendVerifyMessage(ctx context.Context, userCred mcclient.TokenCredential, v return nil } -func UpdateDingtalk(uid string) { +func PullContact(uid string, contactType string) { workMan.Run(func() { - updateDingtalk(context.Background(), uid) + pullContact(context.Background(), uid, contactType) }, nil, nil) } -func updateDingtalk(ctx context.Context, uid string) { - contacts, err := ContactManager.FetchByUIDAndCType(uid, []string{MOBILE, DINGTALK}) +func pullContact(ctx context.Context, uid string, contactType string) { + contacts, err := ContactManager.FetchByUIDAndCType(uid, []string{MOBILE, contactType}) if err != nil { log.Errorf("fetch contacts error") } if len(contacts) == 0 { return } - var mobileContact, dingtalkContact *SContact + var mobileContact, subContact *SContact for i := range contacts { if contacts[i].ContactType == MOBILE { mobileContact = &contacts[i] } else { - dingtalkContact = &contacts[i] + subContact = &contacts[i] } } if mobileContact == nil { return } - userid, err := NotifyService.ContactByMobile(ctx, mobileContact.Contact, DINGTALK) + userid, err := NotifyService.ContactByMobile(ctx, mobileContact.Contact, contactType) if err != nil { - log.Errorf("fetch dingtalk userid by mobile failed: %s", err.Error()) + log.Errorf("fetch %s contact by mobile failed: %s", contactType, err.Error()) } - if dingtalkContact != nil { - dingtalkContact.SetModelManager(ContactManager, dingtalkContact) - origin := dingtalkContact.Contact - _, err := db.Update(dingtalkContact, func() error { - dingtalkContact.Contact = userid + if subContact != nil { + subContact.SetModelManager(ContactManager, subContact) + origin := subContact.Contact + _, err := db.Update(subContact, func() error { + subContact.Contact = userid return nil }) if err != nil { - log.Errorf("update dingtalk userid %s => %s failed", origin, userid) + log.Errorf("update %s contact userid %s => %s failed", contactType, origin, userid) } return } contact := SContact{ UID: uid, - ContactType: DINGTALK, + ContactType: contactType, Contact: userid, Enabled: "1", VerifiedAt: time.Now(), } contact.Status = CONTACT_VERIFIED - err = ContactManager.TableSpec().InsertOrUpdate(&contact) + err = ContactManager.TableSpec().Insert(&contact) if err != nil { - log.Errorf("create new dingtalk contact failed") + log.Errorf("create new %s contact failed", contactType) } } diff --git a/pkg/notify/rpc/send.go b/pkg/notify/rpc/send.go index c7c4b48c85..23bfea9dca 100644 --- a/pkg/notify/rpc/send.go +++ b/pkg/notify/rpc/send.go @@ -82,7 +82,7 @@ func (self *SRpcService) InitAll() error { } } if self.SendServices.Len() == 0 { - log.Errorf("No available send service.") + log.Infof("No available send service.") } else { log.Infof("Total %d send service init successful", self.SendServices.Len()) } @@ -285,12 +285,14 @@ func (self *SRpcService) startNewService(ctx context.Context, serviceName string _, err = sendService.UpdateConfig(ctx, &args) if err != nil { st := status.Convert(err) - if st.Code() == codes.Unavailable { + if st.Code() == codes.FailedPrecondition { // no such rpc serve - os.Remove(filename) - return nil, fmt.Errorf("no such rpc serve") + err = fmt.Errorf(st.Message()) } - return nil, fmt.Errorf(st.Message()) + if st.Code() == codes.Unavailable { + err = fmt.Errorf("service is unavailable for now: %s", st.Message()) + } + return nil, errors.Wrap(err, "UpdateConfig") } return sendService, nil