Compare commits

...

62 Commits

Author SHA1 Message Date
Zexi Li
6254b64d5d Merge pull request #9886 from ioito/automated-cherry-pick-of-#9856-upstream-release-3.6
Automated cherry pick of #9856: fix(region): optimized azure reset linux pasword
2021-01-14 17:39:46 +08:00
Zexi Li
43f8ac0027 Merge pull request #9884 from rainzm/automated-cherry-pick-of-#9882-upstream-release-3.6
Automated cherry pick of #9882: Fix Notifition about Panic_Error Send too much
2021-01-14 14:50:07 +08:00
Qu Xuan
36bd1baafe fix(region): optimized azure reset linux pasword 2021-01-14 14:44:20 +08:00
rainzm
98ca80e630 feat(notify): change default verify expire interval 5m => 2m 2021-01-14 14:26:04 +08:00
rainzm
a2613e6bce fix(notify): update receiver's lang when update event occurs 2021-01-14 14:26:04 +08:00
rainzm
a8b14bb739 fix(notify): panic for err 2021-01-14 14:26:04 +08:00
Zexi Li
4850c4f8e2 Merge pull request #9880 from zhaoxiangchun/automated-cherry-pick-of-#9879-upstream-release-3.6
Automated cherry pick of #9879: feat(monitor): influxQuery add P95 reduce
2021-01-14 11:05:29 +08:00
zhaoxiangchun
64dec4254f feat(monitor): influxQuery add P95 reduce 2021-01-14 10:48:34 +08:00
Zexi Li
ada90754f9 Merge pull request #9874 from swordqiu/automated-cherry-pick-of-#9872-upstream-release-3.6
Automated cherry pick of #9872: fix(region): AWS buckets in us-east-1 have null location
2021-01-14 09:36:28 +08:00
Qiu Jian
fb3456c42d fix(region): AWS buckets in us-east-1 have null location 2021-01-14 09:12:35 +08:00
Zexi Li
cefbeb7a14 Merge pull request #9867 from zhaoxiangchun/automated-cherry-pick-of-#9865-upstream-release-3.6
Automated cherry pick of #9865: feat(monitor): support monitor overview influxdb query pass through
2021-01-13 21:12:20 +08:00
zhaoxiangchun
1fc01ebe94 feat(monitor): support monitor overview influxdb query pass through
1.监控总览支持查询穿透
2021-01-13 16:19:55 +08:00
Zexi Li
ff0606f166 Merge pull request #9864 from rainzm/automated-cherry-pick-of-#9862-upstream-release-3.6
Automated cherry pick of #9862: fix(notify): set status after sending
2021-01-13 16:10:46 +08:00
rainzm
83dd534241 fix(notify): set status after sending 2021-01-13 15:49:15 +08:00
Zexi Li
a7771885cf Merge pull request #9859 from rainzm/automated-cherry-pick-of-#9857-upstream-release-3.6
Automated cherry pick of #9857: fix(notify): set ModelManager in Receiver()
2021-01-13 14:41:34 +08:00
rainzm
88fa743e4e fix(notify): set ModelManager in Receiver() 2021-01-13 09:56:28 +08:00
Zexi Li
0e917bb275 Merge pull request #9849 from rainzm/automated-cherry-pick-of-#9847-upstream-release-3.6
Automated cherry pick of #9847: feat(notify): send notifications of different templates according to language
2021-01-12 10:29:51 +08:00
Zexi Li
cd76ce27c3 Merge pull request #9838 from swordqiu/automated-cherry-pick-of-#9836-upstream-release-3.6
Automated cherry pick of #9836: fix(keystone): add suggestion previliges to meter admin
2021-01-12 10:26:35 +08:00
Zexi Li
114de75e7e Merge pull request #9834 from ioito/automated-cherry-pick-of-#9833-upstream-release-3.6
Automated cherry pick of #9833: fix(cloudid): multi cloudaccount with saml user
2021-01-12 10:25:25 +08:00
Zexi Li
047509ebb8 Merge pull request #9843 from tb365/automated-cherry-pick-of-#9841-upstream-release-3.6
Automated cherry pick of #9841: server skus filter by mem size fix
2021-01-11 22:37:36 +08:00
Zexi Li
71e79e5a7b Merge pull request #9846 from yousong/automated-cherry-pick-of-#9844-upstream-release-3.6
Automated cherry pick of #9844: Feature/yousong i18n apigateway
2021-01-11 22:27:49 +08:00
Zexi Li
4c62508b01 Merge pull request #9852 from zhaoxiangchun/automated-cherry-pick-of-#9851-upstream-release-3.6
Automated cherry pick of #9851: fix(monitor): monitor overview throw err
2021-01-11 22:25:39 +08:00
zhaoxiangchun
e5b637906c fix(monitor): monitor overview throw err 2021-01-11 21:26:16 +08:00
rainzm
dd7d34402f feat(notify): send notifications of different templates according to language 2021-01-11 19:46:03 +08:00
Yousong Zhou
157bfb696b apigateway: try return httperrors 2021-01-11 18:35:51 +08:00
Yousong Zhou
464e0450f9 locales: generate 2021-01-11 18:35:51 +08:00
Yousong Zhou
a4b5562bd0 locales: zh-CN: add some translations 2021-01-11 18:35:51 +08:00
Yousong Zhou
f0354d0c9b locales: generate 2021-01-11 18:29:14 +08:00
Yousong Zhou
c3c3c00759 locales: zh-CN: extract current 2021-01-11 18:27:30 +08:00
Yousong Zhou
83adeaae4e apigateway: reword error messages 2021-01-11 17:51:33 +08:00
Yousong Zhou
d7f05e983f keystone: reword error messages 2021-01-11 17:51:33 +08:00
tangbin
5d075bbaaf server skus filter by mem size fix 2021-01-11 14:41:51 +08:00
Qiu Jian
86fda0f7d0 fix(keystone): add suggestion previliges to meter admin 2021-01-11 02:07:44 +08:00
Qu Xuan
53c8c16052 fix(cloudid): multi cloudaccount with saml user 2021-01-10 20:00:25 +08:00
Zexi Li
46ba9cd424 Merge pull request #9831 from tb365/automated-cherry-pick-of-#9830-upstream-release-3.6
Automated cherry pick of #9830: loadbalancer add lb purge validate
2021-01-09 19:58:29 +08:00
Zexi Li
ee59f2a410 Merge pull request #9795 from tb365/automated-cherry-pick-of-#9793-upstream-release-3.6
Automated cherry pick of #9793: feat(region): server sku os_arch & cpu_arch checking
2021-01-09 19:57:17 +08:00
Zexi Li
5cfbb416b3 Merge pull request #9818 from zhaoxiangchun/automated-cherry-pick-of-#9817-upstream-release-3.6
Automated cherry pick of #9817: fix(monitor): fix monitoroverview and meter send alerting info to  notify
2021-01-09 19:56:05 +08:00
Zexi Li
a711f832e6 Merge pull request #9827 from rainzm/automated-cherry-pick-of-#9825-upstream-release-3.6
Automated cherry pick of #9825: fix(notify): avoid duplication of notification names
2021-01-09 19:54:51 +08:00
tangbin
f31216be0e loadbalancer add lb purge validate 2021-01-09 19:20:36 +08:00
rainzm
bc0106902a fix(notify): avoid duplication of notification names 2021-01-09 18:46:52 +08:00
yunion-ci-robot
2dbfc80fac Merge pull request #9810 from tb365/automated-cherry-pick-of-#9808-upstream-release-3.6
Automated cherry pick of #9808: fix(region): aws elb backendgroup sync bugfix
2021-01-09 16:55:09 +08:00
tangbin
4fa95bf608 fix(region): aws elb backendgroup sync bugfix 2021-01-09 16:12:44 +08:00
zhaoxiangchun
af0813cd09 fix(monitor): fix monitoroverview and meter send alerting info to notify
1.调整报警总览中今日报警数量计算逻辑
2.报警模版的web路径支持跳转到不同web页面:监控报警和meter
3.nodata 前端可配制功能fix
2021-01-09 15:34:04 +08:00
Zexi Li
3b5602e1ea Merge pull request #9800 from ioito/automated-cherry-pick-of-#9798-upstream-release-3.6
Automated cherry pick of #9798: fix(region): optimized for qcloud prepaid instance delete
2021-01-09 15:33:14 +08:00
Zexi Li
e4f7d3233c Merge pull request #9815 from rainzm/automated-cherry-pick-of-#9813-upstream-release-3.6
Automated cherry pick of #9813: fix(notify): loose phone number rules in updating receiver
2021-01-09 13:03:52 +08:00
rainzm
287f98ac86 fix(notify): loose phone number rules in updating receiver 2021-01-09 11:32:31 +08:00
Zexi Li
bb44f1cdfb Merge pull request #9804 from rainzm/automated-cherry-pick-of-#9802-upstream-release-3.6
Automated cherry pick of #9802: fix(notify): loose mobile phone number matching rules
2021-01-08 14:19:50 +08:00
rainzm
644ab6f925 fix(notify): loose mobile phone number matching rules 2021-01-08 12:03:00 +08:00
Qu Xuan
e775e798aa fix(region): optimized for qcloud prepaid instance delete 2021-01-08 11:32:58 +08:00
tangbin
331d9d2a67 feat(region): server sku os_arch & cpu_arch checking 2021-01-08 10:35:08 +08:00
Zexi Li
d5ed96a651 Merge pull request #9792 from wanyaoqi/automated-cherry-pick-of-#9790-upstream-release-3.6
Automated cherry pick of #9790: fix(host-deployer): add retry on check is vgactived
2021-01-07 21:18:13 +08:00
Zexi Li
4ce85f310b Merge pull request #9787 from yousong/automated-cherry-pick-of-#9785-upstream-release-3.6
Automated cherry pick of #9785: guest_deploy_task: log other deploy details with i18n
2021-01-07 21:16:39 +08:00
Zexi Li
3f6e669324 Merge pull request #9784 from zhaoxiangchun/automated-cherry-pick-of-#9782-upstream-release-3.6
Automated cherry pick of #9782: fix(monitor,suggestion): order commonalert metric and modify alertresoruce type value and add suggestion models climc
2021-01-07 21:14:46 +08:00
wanyaoqi
3f4b2163f3 fix(host-deployer): add retry on check is vgactived 2021-01-07 19:28:09 +08:00
Yousong Zhou
78b6f85662 guest_deploy_task: log other deploy details with i18n
Fixes 9f350392 ("add service tag for actionlog")
2021-01-07 11:40:27 +08:00
zhaoxiangchun
3124113e2f fix(monitor,suggestion): order commonalert metric and modify alertresoruce type value and add suggestion models climc
1.修改监控metric排序
2.统一报警资源type和监控报警中res_type 进行统一,前端可以统一展示
3.增加suggestion服务相关climc
2021-01-06 21:31:31 +08:00
Zexi Li
38b34239cd Merge pull request #9780 from ioito/automated-cherry-pick-of-#9778-upstream-release-3.6
Automated cherry pick of #9778: fix(region): avoid auzre request timeout
2021-01-06 21:23:27 +08:00
Zexi Li
370b35852c Merge pull request #9777 from ioito/automated-cherry-pick-of-#9775-upstream-release-3.6
Automated cherry pick of #9775: fix(cloudevent): default policy
2021-01-06 21:23:08 +08:00
Zexi Li
f7c8bbc00b Merge pull request #9773 from ioito/automated-cherry-pick-of-#9771-upstream-release-3.6
Automated cherry pick of #9771: fix(region): avoid panic when register service not work
2021-01-06 21:21:47 +08:00
Qu Xuan
f64c6c54da fix(region): avoid auzre request timeout 2021-01-06 15:34:14 +08:00
Qu Xuan
c4db4bc43e fix(cloudevent): default policy 2021-01-06 14:05:35 +08:00
Qu Xuan
1afe6ef41b fix(region): avoid panic when register service not work 2021-01-05 21:20:04 +08:00
60 changed files with 13727 additions and 12596 deletions

View File

@@ -47,7 +47,7 @@
</td>
</tr>
<tr>
<td style="padding-bottom: 10px;">您正在验证邮箱,请在验证码输入框中输入:{{.code}}完成验证。</td>
<td style="padding-bottom: 10px;">您正在验证邮箱,请在验证码输入框中输入:{{.code}}完成验证。</td>
</tr>
</table>
</td>

View File

@@ -0,0 +1 @@
{{.os_type}} image {{.name}} upload completed

View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<table style="width: 650px; margin-bottom: 20px;" border="0" cellpadding="0" cellspacing="0" align="center">
<tr style="height: 50px; background: #333; overflow: hidden;">
<td>
<table style="margin-left: 20px;">
<tr>
<td>
<img src="data:{{.login_logo_format}};base64,{{.login_logo}}" alt="" style="color: #fff; height: 32px; vertical-align: middle;">
</td>
</tr>
</table>
</td>
<td>
<table style="float: right;">
<tr>
<td style="padding-left: 20px; padding-right: 20px;">
<a href="" style="text-decoration: none; color: #20A0FF; font-size: 12px; display: none;">Product</a>
</td>
<td style="padding-left: 20px; padding-right: 20px;">
<a href="" style="text-decoration: none; color: #20A0FF; font-size: 12px; display: none;">Solution</a>
</td>
<td style="padding-left: 20px; padding-right: 20px;">
<a href="" style="text-decoration: none; color: #20A0FF; font-size: 12px; display: none;">Service</a>
</td>
<td style="padding-left: 20px; padding-right: 20px;">
<a href="" style="text-decoration: none; color: #20A0FF; font-size: 12px; display: none;">Help and Documentation</a>
</td>
<td style="padding-left: 20px; padding-right: 20px;">
<a href="" style="text-decoration: none; color: #20A0FF; font-size: 12px; display: none;">About</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="width: 100%;" colspan="2">
<table style="padding: 20px 10px; width: 100%;">
<tr>
<td style="padding-bottom: 13px;">
Dear {{.name}}
</td>
</tr>
<tr>
<td style="padding-bottom: 13px;">You are verifying your email, please enter the following code on the email verification page:</td>
</tr>
<tr>
<td style="padding-bottom: 10px; font-size: large">{{.code}}</td>
</tr>
</table>
</td>
</tr>
<tr style="width: 96%;">
<td colspan="2" style="border-top: 1px dashed #ccc; color: #333; font-size: 12px; padding-bottom: 10px; font-weight: 100;">If you are not operating by yourself, please log in to the platform in time and change your password to ensure the security of your account.</td>
</tr>
<tr>
<td colspan="2" style="background: #333; text-align: right; padding-right: 20px; font-size: 12px; color: #fff; height: 50px;">Copyrights © {{.copyright}}. All rights reserved.</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1 @@
Image {{.name}} upload completed

View File

@@ -0,0 +1 @@
{{.copyright}} Verify

View File

@@ -25,4 +25,6 @@ func init() {
cmd.List(new(options.SuggestSysAlertListOptions))
cmd.Show(new(options.SSuggestAlertShowOptions))
cmd.Perform("ignore", new(options.SuggestAlertIgnoreOptions))
cmd_ := shell.NewResourceCmd(monitor.SuggestSysAlertCostManager)
cmd_.Get("", new(options.SuggestAlertCostOptions))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -501,34 +501,34 @@ func (h *AuthHandlers) doLogin(ctx context.Context, w http.ResponseWriter, req *
if body.Contains("tenantId") { // switch project
token, authToken, err = doTenantLogin(ctx, req, body)
if err != nil {
return errors.Wrap(err, "doTenantLogin")
return err
}
userInfo, err = fetchUserInfoFromToken(ctx, req, token)
if err != nil {
return errors.Wrap(err, "fetchUserInfoFromToken")
return err
}
} else {
// user/password authenticate
// SSO authentication
token, err = h.doCredentialLogin(ctx, req, body)
if err != nil {
return errors.Wrap(err, "doCredentialLogin")
return err
}
userInfo, err = fetchUserInfoFromToken(ctx, req, token)
if err != nil {
return errors.Wrap(err, "fetchUserInfoFromToken")
return err
}
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
isTotpInit, err := isUserTotpCredInitialed(s, token.GetUserId())
if err != nil {
return errors.Wrap(err, "isUserTotpCredInitialed")
return err
}
isIdpLogin := body.Contains("idp_driver")
authToken = clientman.NewAuthToken(token.GetTokenString(), isUserEnableTotp(userInfo), isTotpInit, isIdpLogin)
}
if !isUserAllowWebconsole(userInfo) {
return errors.Wrap(httperrors.ErrForbidden, "user forbidden login from web")
return httperrors.NewForbiddenError("user forbidden login from web")
}
saveAuthCookie(w, authToken, token)
@@ -996,7 +996,7 @@ func (h *AuthHandlers) getPermissionDetails(ctx context.Context, w http.Response
_, query, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(ctx, w, "body is empty")
httperrors.InvalidInputError(ctx, w, "request body is empty")
return
}
var name string
@@ -1030,7 +1030,7 @@ func (h *AuthHandlers) doCreatePolicies(ctx context.Context, w http.ResponseWrit
// }
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(ctx, w, "body is empty")
httperrors.InvalidInputError(ctx, w, "request body is empty")
return
}
s := auth.GetSession(ctx, t, FetchRegion(req), "")
@@ -1114,7 +1114,7 @@ func (h *AuthHandlers) resetUserPassword(ctx context.Context, w http.ResponseWri
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(ctx, w, "body is empty")
httperrors.InvalidInputError(ctx, w, "request body is empty")
return
}
@@ -1150,7 +1150,7 @@ func (h *AuthHandlers) resetUserPassword(ctx context.Context, w http.ResponseWri
return
}
}
httperrors.InputParameterError(ctx, w, "密码错误")
httperrors.InputParameterError(ctx, w, "wrong password")
return
}

View File

@@ -205,7 +205,7 @@ func validateTotpRecoverySecrets(s *mcclient.ClientSession, uid string, question
func initTotpSecrets(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t, authToken, err := fetchAuthInfo(ctx, req)
if err != nil {
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail %s", err)
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail: %s", err)
return
}
if authToken.IsTotpInitialized() {
@@ -232,14 +232,14 @@ func initTotpSecrets(ctx context.Context, w http.ResponseWriter, req *http.Reque
func validatePasscodeHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t, authToken, err := fetchAuthInfo(ctx, req)
if err != nil {
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail %s", err)
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail: %s", err)
return
}
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(ctx, w, "body is empty")
httperrors.InvalidInputError(ctx, w, "request body is empty")
return
}
@@ -271,14 +271,14 @@ func validatePasscodeHandler(ctx context.Context, w http.ResponseWriter, req *ht
func resetTotpSecrets(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t, _, err := fetchAuthInfo(ctx, req)
if err != nil {
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail %s", err)
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail: %s", err)
return
}
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(ctx, w, "body is empty")
httperrors.InvalidInputError(ctx, w, "request body is empty")
return
}
@@ -309,7 +309,7 @@ func resetTotpSecrets(ctx context.Context, w http.ResponseWriter, req *http.Requ
func listTotpRecoveryQuestions(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t, _, err := fetchAuthInfo(ctx, req)
if err != nil {
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail %s", err)
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail: %s", err)
return
}
@@ -335,14 +335,14 @@ func listTotpRecoveryQuestions(ctx context.Context, w http.ResponseWriter, req *
func resetTotpRecoveryQuestions(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t, _, err := fetchAuthInfo(ctx, req)
if err != nil {
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail %s", err)
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail: %s", err)
return
}
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(ctx, w, "body is empty")
httperrors.InvalidInputError(ctx, w, "request body is empty")
return
}

View File

@@ -170,7 +170,7 @@ func (h *AuthHandlers) handleSsoLogin(ctx context.Context, w http.ResponseWriter
case "POST":
formData, err := appsrv.Fetch(req)
if err != nil {
httperrors.InputParameterError(ctx, w, "fetch formdata error: %s", err)
httperrors.InputParameterError(ctx, w, "fetch form data error: %s", err)
}
body, err = jsonutils.ParseQueryString(string(formData))
if err != nil {

View File

@@ -42,6 +42,8 @@ import (
"yunion.io/x/onecloud/pkg/util/httputils"
)
const contentTypeSpreadsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
const (
HOST_MAC = "*MAC地址"
HOST_NAME = "*名称"
@@ -154,8 +156,8 @@ func (mh *MiscHandler) DoBatchHostRegister(ctx context.Context, w http.ResponseW
fileHeader := hostfiles[0].Header
contentType := fileHeader.Get("Content-Type")
if contentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
e := httperrors.NewInputParameterError("Wrong content type %s, required application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", contentType)
if contentType != contentTypeSpreadsheet {
e := httperrors.NewInputParameterError("Wrong content type %s, want %s", contentType, contentTypeSpreadsheet)
httperrors.JsonClientError(ctx, w, e)
return
}
@@ -199,7 +201,7 @@ func (mh *MiscHandler) DoBatchHostRegister(ctx context.Context, w http.ResponseW
}
if !titlesOk {
httperrors.InputParameterError(ctx, w, "template file is invalid.please check.")
httperrors.InputParameterError(ctx, w, "template file is invalid. please check.")
return
}
@@ -302,8 +304,8 @@ func (mh *MiscHandler) DoBatchUserRegister(ctx context.Context, w http.ResponseW
fileHeader := userfiles[0].Header
contentType := fileHeader.Get("Content-Type")
if contentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
e := httperrors.NewInputParameterError("Wrong content type %s, required application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", contentType)
if contentType != contentTypeSpreadsheet {
e := httperrors.NewInputParameterError("Wrong content type %s, want %s", contentType, contentTypeSpreadsheet)
httperrors.JsonClientError(ctx, w, e)
return
}
@@ -328,7 +330,7 @@ func (mh *MiscHandler) DoBatchUserRegister(ctx context.Context, w http.ResponseW
// skipped header row
rows := xlsx.GetRows("users")
if len(rows) <= 1 {
e := httperrors.NewInputParameterError("empty file")
e := httperrors.NewInputParameterError("empty file content")
httperrors.JsonClientError(ctx, w, e)
return
} else if len(rows) > BATCH_USER_REGISTER_QUANTITY_LIMITATION {
@@ -431,7 +433,7 @@ func (mh *MiscHandler) getDownloadsHandler(ctx context.Context, w http.ResponseW
params := appctx.AppContextParams(ctx)
template, ok := params["<template_id>"]
if !ok || len(template) == 0 {
httperrors.InvalidInputError(ctx, w, "not found")
httperrors.InvalidInputError(ctx, w, "template_id")
return
}

View File

@@ -142,11 +142,11 @@ func fetchIdList(ctx context.Context, query jsonutils.JSONObject, w http.Respons
if e == nil && len(idlist) > 0 {
queryDict := query.(*jsonutils.JSONDict)
queryDict.Remove("id")
log.Debugf("Get idlist: %s", idlist)
log.Debugf("Get id list: %s", idlist)
return jsonutils.JSONArray2StringArray(idlist)
} else {
log.Debugf("Cannot find idlist in query: %s", query)
httperrors.InvalidInputError(ctx, w, "No idlist found")
log.Debugf("Cannot find id list in query: %s", query)
httperrors.InvalidInputError(ctx, w, "No id list found")
return nil
}
}

View File

@@ -22,7 +22,7 @@ type AlertResourceType string
const (
// AlertResourceTypeNode means onecloud system infrastructure controller or host node
AlertResourceTypeNode AlertResourceType = "node"
AlertResourceTypeNode AlertResourceType = "host"
// AlertResourceTypeCloudaccount means cloudaccount resource
AlertResourceTypeCloudaccount AlertResourceType = "cloudaccount"
// AlertResourceTypeVM means virtual machine guest resource

View File

@@ -120,10 +120,11 @@ type CommonAlertDetails struct {
}
type CommonAlertMetricDetails struct {
Comparator string `json:"comparator"`
Threshold float64 `json:"threshold"`
ConditionType string `json:"condition_type"`
ThresholdStr string `json:"threshold_str"`
Comparator string `json:"comparator"`
Threshold float64 `json:"threshold"`
WithinRange []float64 `json:"within_range"`
ConditionType string `json:"condition_type"`
ThresholdStr string `json:"threshold_str"`
// metric points'value的运算方式
Reduce string `json:"reduce"`
DB string `json:"db"`

View File

@@ -30,8 +30,8 @@ var (
MetricUnit = []string{METRIC_UNIT_PERCENT, METRIC_UNIT_BPS, METRIC_UNIT_MBPS, METRIC_UNIT_BYTEPS, "count/s",
METRIC_UNIT_COUNT, METRIC_UNIT_MS, METRIC_UNIT_BYTE, METRIC_UNIT_RMB}
ResTypeScoreMap = map[string]int{
METRIC_RES_TYPE_HOST: 1,
METRIC_RES_TYPE_GUEST: 2,
METRIC_RES_TYPE_GUEST: 1,
METRIC_RES_TYPE_HOST: 2,
METRIC_RES_TYPE_OSS: 3,
METRIC_RES_TYPE_RDS: 4,
METRIC_RES_TYPE_REDIS: 5,

View File

@@ -62,6 +62,9 @@ const (
TEMPLATE_TYPE_CONTENT = "content"
TEMPLATE_TYPE_REMOTE = "remote"
TEMPLATE_LANG_EN = "en"
TEMPLATE_LANG_CN = "cn"
CTYPE_ROBOT_YES = "yes"
CTYPE_ROBOT_ONLY = "only"
)

View File

@@ -22,7 +22,7 @@ import (
var (
cloudeventSystemResources = []string{}
cloudeventDomainResources = []string{
"cloudevent",
"cloudevents",
}
cloudeventUserResources = []string{}
)

View File

@@ -127,16 +127,6 @@ func (manager *SSamluserManager) ValidateCreateData(ctx context.Context, userCre
return input, err
}
group := _group.(*SCloudgroup)
sq := CloudgroupManager.Query("id").Equals("provider", group.Provider).SubQuery()
q := manager.Query().Equals("owner_id", input.OwnerId).In("cloudgroup_id", sq)
groups := []SCloudgroup{}
err = db.FetchModelObjects(CloudgroupManager, q, &groups)
if err != nil {
return input, httperrors.NewGeneralError(errors.Wrapf(err, "db.FetchModelObjects"))
}
if len(groups) > 0 {
return input, httperrors.NewConflictError("user %s has already in other %s group", input.Name, group.Provider)
}
_account, err := validators.ValidateModel(userCred, CloudaccountManager, &input.CloudaccountId)
if err != nil {
return input, err
@@ -148,6 +138,17 @@ func (manager *SSamluserManager) ValidateCreateData(ctx context.Context, userCre
if account.Provider != group.Provider {
return input, httperrors.NewConflictError("account %s and group %s not with same provider", account.Name, group.Name)
}
sq := CloudgroupManager.Query("id").Equals("provider", group.Provider).SubQuery()
q := manager.Query().Equals("owner_id", input.OwnerId).Equals("cloudaccount_id", account.Id).In("cloudgroup_id", sq)
groups := []SCloudgroup{}
err = db.FetchModelObjects(CloudgroupManager, q, &groups)
if err != nil {
return input, httperrors.NewGeneralError(errors.Wrapf(err, "db.FetchModelObjects"))
}
if len(groups) > 0 {
return input, httperrors.NewConflictError("user %s has already in other %s group", input.Name, group.Provider)
}
input.Status = api.SAML_USER_STATUS_AVAILABLE
return input, nil
}

View File

@@ -1324,6 +1324,13 @@ func (manager *SGuestManager) validateCreateData(
}
log.Debugf("ROOT DISK: %#v", rootDiskConfig)
input.Disks[0] = rootDiskConfig
if sku != nil {
if len(rootDiskConfig.OsArch) >= 0 && len(sku.CpuArch) >= 0 {
if strings.Contains(rootDiskConfig.OsArch, sku.CpuArch) {
return nil, httperrors.NewConflictError("root disk image(%s) and sku(%s) architecture mismatch", rootDiskConfig.OsArch, sku.CpuArch)
}
}
}
//data.Set("disk.0", jsonutils.Marshal(rootDiskConfig))
for i := 0; i < len(dataDiskDefs); i += 1 {

View File

@@ -184,9 +184,9 @@ func (man *SAwsCachedLbbgManager) GetCachedBackendGroups(backendGroupId string)
return ret, nil
}
func (man *SAwsCachedLbbgManager) getLoadbalancerBackendgroupsByRegion(regionId string) ([]SAwsCachedLbbg, error) {
func (man *SAwsCachedLbbgManager) getLoadbalancerBackendgroupsByRegion(managerId string, regionId string) ([]SAwsCachedLbbg, error) {
lbbgs := []SAwsCachedLbbg{}
q := man.Query().Equals("cloudregion_id", regionId).IsFalse("pending_deleted")
q := man.Query().Equals("cloudregion_id", regionId).Equals("manager_id", managerId).IsFalse("pending_deleted")
if err := db.FetchModelObjects(man, q, &lbbgs); err != nil {
log.Errorf("failed to get lbbgs for region: %s error: %v", regionId, err)
return nil, err
@@ -204,7 +204,7 @@ func (man *SAwsCachedLbbgManager) SyncLoadbalancerBackendgroups(ctx context.Cont
remoteLbbgs := []cloudprovider.ICloudLoadbalancerBackendGroup{}
syncResult := compare.SyncResult{}
dbLbbgs, err := man.getLoadbalancerBackendgroupsByRegion(region.GetId())
dbLbbgs, err := man.getLoadbalancerBackendgroupsByRegion(provider.GetId(), region.GetId())
if err != nil {
syncResult.Error(err)
return nil, nil, syncResult

View File

@@ -716,6 +716,19 @@ func (lb *SLoadbalancer) getMoreDetails(out api.LoadbalancerDetails) (api.Loadba
}
func (lb *SLoadbalancer) ValidateDeleteCondition(ctx context.Context) error {
err := lb.validatePurgeCondition(ctx)
if err != nil {
return err
}
if lb.DisableDelete.IsTrue() {
return httperrors.NewInvalidStatusError("loadbalancer is locked, cannot delete")
}
return nil
}
func (lb *SLoadbalancer) validatePurgeCondition(ctx context.Context) error {
region := lb.GetRegion()
if region != nil {
if err := region.GetDriver().ValidateDeleteLoadbalancerCondition(ctx, lb); err != nil {
@@ -723,10 +736,6 @@ func (lb *SLoadbalancer) ValidateDeleteCondition(ctx context.Context) error {
}
}
if lb.DisableDelete.IsTrue() {
return httperrors.NewInvalidStatusError("loadbalancer is locked, cannot delete")
}
return lb.SModelBase.ValidateDeleteCondition(ctx)
}
@@ -790,11 +799,9 @@ func (lb *SLoadbalancer) Delete(ctx context.Context, userCred mcclient.TokenCred
func (man *SLoadbalancerManager) getLoadbalancersByRegion(region *SCloudregion, provider *SCloudprovider) ([]SLoadbalancer, error) {
lbs := []SLoadbalancer{}
vpcs := VpcManager.Query().SubQuery()
q := man.Query()
q = q.Join(vpcs, sqlchemy.Equals(q.Field("vpc_id"), vpcs.Field("id")))
q = q.Filter(sqlchemy.Equals(vpcs.Field("cloudregion_id"), region.Id))
q = q.Filter(sqlchemy.Equals(vpcs.Field("manager_id"), provider.Id))
q = q.Equals("manager_id", provider.Id)
q = q.Equals("cloudregion_id", region.Id)
q = q.IsFalse("pending_deleted")
if err := db.FetchModelObjects(man, q, &lbs); err != nil {
log.Errorf("failed to get lbs for region: %v provider: %v error: %v", region, provider, err)
@@ -816,6 +823,12 @@ func (man *SLoadbalancerManager) getLoadbalancersByExternalIds(externalIds []str
}
func (man *SLoadbalancerManager) getLocalLoadbalancers(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, region *SCloudregion, lbs []cloudprovider.ICloudLoadbalancer) ([]SLoadbalancer, error) {
// current external ID
extIds := []string{}
for i := range lbs {
extIds = append(extIds, lbs[i].GetGlobalId())
}
part1, err := man.getLoadbalancersByRegion(region, provider)
if err != nil {
return nil, err
@@ -824,15 +837,13 @@ func (man *SLoadbalancerManager) getLocalLoadbalancers(ctx context.Context, user
localLbs := map[string]SLoadbalancer{}
for i := range part1 {
localLbs[part1[i].Id] = part1[i]
if len(part1[i].GetExternalId()) > 0 {
extIds = append(extIds, part1[i].GetExternalId())
}
}
externalIds := []string{}
for i := range lbs {
externalIds = append(externalIds, lbs[i].GetGlobalId())
}
if len(externalIds) > 0 {
part2, err := man.getLoadbalancersByExternalIds(externalIds)
if len(extIds) > 0 {
part2, err := man.getLoadbalancersByExternalIds(extIds)
if err != nil {
return nil, err
}
@@ -842,7 +853,7 @@ func (man *SLoadbalancerManager) getLocalLoadbalancers(ctx context.Context, user
}
}
ret := []SLoadbalancer{}
ret := make([]SLoadbalancer, 0)
for id, _ := range localLbs {
ret = append(ret, localLbs[id])
}
@@ -860,7 +871,7 @@ func (man *SLoadbalancerManager) SyncLoadbalancers(ctx context.Context, userCred
remoteLbs := []cloudprovider.ICloudLoadbalancer{}
syncResult := compare.SyncResult{}
dbLbs, err := man.getLocalLoadbalancers(ctx, userCred, provider, region, remoteLbs)
dbLbs, err := man.getLocalLoadbalancers(ctx, userCred, provider, region, lbs)
if err != nil {
syncResult.Error(err)
return nil, nil, syncResult
@@ -1007,7 +1018,7 @@ func (lb *SLoadbalancer) syncRemoveCloudLoadbalancer(ctx context.Context, userCr
lockman.LockObject(ctx, lb)
defer lockman.ReleaseObject(ctx, lb)
err := lb.ValidateDeleteCondition(ctx)
err := lb.validatePurgeCondition(ctx)
if err != nil { // cannot delete
return lb.SetStatus(userCred, api.LB_STATUS_UNKNOWN, "sync to delete")
} else {

View File

@@ -88,8 +88,9 @@ type SServerSku struct {
PrepaidStatus string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"admin_optional" default:"available"` // 预付费资源状态 available|soldout
PostpaidStatus string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"admin_optional" default:"available"` // 按需付费资源状态 available|soldout
CpuCoreCount int `nullable:"false" list:"user" create:"admin_required"`
MemorySizeMB int `nullable:"false" list:"user" create:"admin_required"`
CpuArch string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"` // CPU 架构 x86|xarm
CpuCoreCount int `nullable:"false" list:"user" create:"admin_required"`
MemorySizeMB int `nullable:"false" list:"user" create:"admin_required"`
OsName string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin" default:"Any"` // Windows|Linux|Any
@@ -834,7 +835,7 @@ func (manager *SServerSkuManager) ListItemFilter(
conditions = append(
conditions,
sqlchemy.AND(
sqlchemy.GE(q.Field("memory_size_mb"), s),
sqlchemy.GT(q.Field("memory_size_mb"), s),
sqlchemy.LE(q.Field("memory_size_mb"), e),
),
)
@@ -1171,6 +1172,7 @@ func (self *SServerSku) syncWithCloudSku(ctx context.Context, userCred mcclient.
self.InstanceTypeCategory = extSku.InstanceTypeCategory
self.PrepaidStatus = extSku.PrepaidStatus
self.PostpaidStatus = extSku.PostpaidStatus
self.CpuArch = extSku.CpuArch
self.SysDiskType = extSku.SysDiskType
self.DataDiskTypes = extSku.DataDiskTypes
return nil

View File

@@ -106,7 +106,7 @@ func (self *GuestDeployTask) OnDeployGuestComplete(ctx context.Context, obj db.I
}
if !_log {
// 如果 deploy 有其他事件,统一记在这里。
logclient.AddActionLogWithStartable(self, guest, "misc部署", "", self.UserCred, true)
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_DEPLOY, action, self.UserCred, true)
}
if self.HasParentTask() {

View File

@@ -88,6 +88,7 @@ func (d *SKVMGuestDisk) MountKvmRootfs() fsdriver.IRootFsDriver {
func (d *SKVMGuestDisk) mountKvmRootfs(readonly bool) fsdriver.IRootFsDriver {
partitions := d.deployer.GetPartitions()
for i := 0; i < len(partitions); i++ {
log.Infof("detect partition %s", partitions[i].GetPartDev())
mountFunc := partitions[i].Mount
if readonly {
mountFunc = partitions[i].MountPartReadOnly

View File

@@ -164,6 +164,7 @@ func (d *NBDDriver) setupLVMS() (bool, error) {
for _, part := range d.partitions {
vgname := d.findLVMPartitions(part.GetPartDev())
if len(vgname) > 0 {
log.Infof("find vg %s from %s", vgname, part.GetPartDev())
lvm := NewKVMGuestLVMPartition(part.GetPartDev(), vgname)
d.lvms = append(d.lvms, lvm)
if lvm.SetupDevice() {

View File

@@ -20,6 +20,7 @@ import (
"regexp"
"strings"
"sync"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/util/stringutils"
@@ -132,13 +133,11 @@ func (p *SKVMGuestLVMPartition) FindPartitions() []*kvmpart.SKVMGuestDiskPartiti
if !p.isVgActive() {
return nil
}
files, err := ioutil.ReadDir("/dev/" + p.vgname)
if err != nil {
log.Errorln(err)
return nil
}
parts := []*kvmpart.SKVMGuestDiskPartition{}
for _, f := range files {
partPath := fmt.Sprintf("/dev/%s/%s", p.vgname, f.Name())
@@ -168,11 +167,15 @@ func (p *SKVMGuestLVMPartition) PutdownDevice() bool {
}
func (p *SKVMGuestLVMPartition) isVgActive() bool {
if fileutils2.Exists("/dev/" + p.vgname) {
return true
} else {
return false
for i := 0; i < 3; i++ {
if fileutils2.Exists("/dev/" + p.vgname) {
log.Infof("vg %s is active", p.vgname)
return true
}
time.Sleep(time.Second * 1)
}
log.Infof("vg %s is not active", p.vgname)
return false
}
func (p *SKVMGuestLVMPartition) vgActivate(activate bool) bool {
@@ -185,6 +188,9 @@ func (p *SKVMGuestLVMPartition) vgActivate(activate bool) bool {
log.Errorf("%s", output)
return false
}
if out, err := procutils.NewCommand("vgchange", "--refresh").Output(); err != nil {
log.Errorf("vgchange refresh failed: %s, %s", out, err)
}
return true
}

View File

@@ -21,11 +21,15 @@ import (
const (
RoleAdmin = "admin"
RoleFA = "fa"
RoleDomainFA = "domainfa"
RoleProjectFA = "projectfa"
RoleSA = "sa"
RoleProjectOwner = "project_owner"
RoleMember = "member"
RoleDomainAdmin = "domainadmin"
RoleProjectEditor = "project_editor"
RoleProjectViewer = "project_viewer"
RoleMember = "member"
)
type sPolicyDefinition struct {
@@ -42,6 +46,7 @@ type SRoleDefiniton struct {
Description string
Policies []string
Project string
IsPublic bool
DescriptionCN string
}
@@ -287,7 +292,8 @@ var (
Desc: "resources of metering and billing service",
Scope: rbacutils.ScopeProject,
Services: map[string][]string{
"meter": nil,
"meter": nil,
"suggestion": nil,
"notify": {
"receivers",
},
@@ -423,7 +429,8 @@ var (
Policies: []string{
"sysadmin",
},
Project: "system",
Project: "system",
IsPublic: false,
},
{
Name: RoleDomainAdmin,
@@ -432,6 +439,7 @@ var (
Policies: []string{
"domainadmin",
},
IsPublic: true,
},
{
Name: RoleProjectOwner,
@@ -440,24 +448,37 @@ var (
Policies: []string{
"projectadmin",
},
IsPublic: true,
},
{
Name: RoleFA,
DescriptionCN: "财务管理员",
DescriptionCN: "系统财务管理员",
Description: "System finance administrator",
Policies: []string{
"sysmeteradmin",
"sysdashboard",
},
IsPublic: false,
},
{
Name: RoleMember,
DescriptionCN: "项目只读成员",
Description: "Project read-only member",
Name: RoleDomainFA,
DescriptionCN: "域财务管理员",
Description: "Domain finance administrator",
Policies: []string{
"projectviewer",
"domainmeteradmin",
"domaindashboard",
},
IsPublic: true,
},
{
Name: RoleProjectFA,
DescriptionCN: "项目财务管理员",
Description: "Project finance administrator",
Policies: []string{
"projectmeteradmin",
"projectdashboard",
},
IsPublic: true,
},
{
Name: RoleProjectEditor,
@@ -467,6 +488,17 @@ var (
"projecteditor",
"projectdashboard",
},
IsPublic: true,
},
{
Name: RoleProjectViewer,
DescriptionCN: "项目只读成员",
Description: "Project read-only member",
Policies: []string{
"projectviewer",
"projectdashboard",
},
IsPublic: true,
},
}
)

View File

@@ -113,7 +113,7 @@ func (manager *SCredentialManager) InitializeData() error {
func (manager *SCredentialManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if !data.Contains("type") {
return nil, httperrors.NewInputParameterError("missing input feild type")
return nil, httperrors.NewInputParameterError("missing input field type")
}
projectId, _ := data.GetString("project_id")
userId := ownerId.GetUserId()

View File

@@ -286,7 +286,7 @@ func (domain *SDomain) ValidatePurgeCondition(ctx context.Context) error {
}
groupCnt, _ := domain.GetGroupCount()
if groupCnt > 0 {
return httperrors.NewInvalidStatusError("group is in use by group")
return httperrors.NewInvalidStatusError("domain is in use by group")
}
projCnt, _ := domain.GetProjectCount()
if projCnt > 0 {

View File

@@ -344,7 +344,7 @@ func (ident *SIdentityProvider) PerformConfig(ctx context.Context, userCred mccl
action := input.Action
changed, err := saveConfigs(userCred, action, ident, opts, nil, nil, api.SensitiveDomainConfigMap)
if err != nil {
return nil, httperrors.NewInternalServerError("saveConfig fail %s", err)
return nil, httperrors.NewInternalServerError("saveConfigs fail %s", err)
}
if changed {
ident.MarkDisconnected(ctx, userCred, fmt.Errorf("change config"))

View File

@@ -207,7 +207,7 @@ func (service *SService) PerformConfig(ctx context.Context, userCred mcclient.To
changed, err = saveConfigs(userCred, action, service, opts, nil, api.MergeServiceConfigOptions(api.CommonWhitelistOptionMap, api.ServiceBlacklistOptionMap), nil)
}
if err != nil {
return nil, httperrors.NewInternalServerError("saveConfig fail %s", err)
return nil, httperrors.NewInternalServerError("saveConfigs fail %s", err)
}
if changed {
diff := SService{ConfigVersion: 1}

View File

@@ -24,6 +24,7 @@ var (
SuggestSysAlertManager *SSuggestSysAlertManager
SuggestSysRuleConfigManager *SSuggestSysRuleConfigManager
InfluxdbShemaManager *SInfluxdbShemaManager
SuggestSysAlertCostManager *SSuggestSysAlertManager
)
func init() {
@@ -31,6 +32,7 @@ func init() {
SuggestSysAlertManager = NewSuggestSysAlertManager()
SuggestSysRuleConfigManager = NewSuggestSysRuleConfigManager()
InfluxdbShemaManager = NewInfluxdbShemaManager()
SuggestSysAlertCostManager = NewSuggestSysAlertCostManager()
for _, m := range []modulebase.IBaseManager{
SuggestSysRuleManager,
SuggestSysAlertManager,
@@ -75,6 +77,15 @@ func NewSuggestSysAlertManager() *SSuggestSysAlertManager {
}
}
func NewSuggestSysAlertCostManager() *SSuggestSysAlertManager {
man := modules.NewSuggestionManager("suggestsysalert", "suggestsysalerts",
[]string{},
[]string{})
return &SSuggestSysAlertManager{
ResourceManager: &man,
}
}
func NewSuggestSysRuleConfigManager() *SSuggestSysRuleConfigManager {
man := modules.NewSuggestionManager("suggestsysruleconfig", "suggestsysruleconfigs",
[]string{"id", "name", "type", "resource_type", "enabled", "ignore_alert"},

View File

@@ -22,7 +22,8 @@ import (
type SuggestSysAlertListOptions struct {
options.BaseListOptions
Type string `help:"Type of suggest rule" choices:"EIP_UNUSED|"`
Type string `help:"Type of suggest rule"`
Currency string `json:"currency"`
}
func (o *SuggestSysAlertListOptions) Params() (jsonutils.JSONObject, error) {
@@ -55,3 +56,17 @@ func (opt *SuggestAlertIgnoreOptions) Params() (jsonutils.JSONObject, error) {
func (o *SuggestAlertIgnoreOptions) GetId() string {
return o.ID
}
type SuggestAlertCostOptions struct {
ID string `help:"ID or name of the alert" json:"-"`
options.BaseListOptions
Currency string `json:"currency"`
}
func (o *SuggestAlertCostOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(o)
}
func (o *SuggestAlertCostOptions) GetId() string {
return o.ID
}

View File

@@ -111,11 +111,18 @@ func (c *NoDataQueryCondition) getOnecloudResources(evalContext *alerting.EvalCo
query := jsonutils.NewDict()
query.Add(jsonutils.NewStringArray([]string{"running", "ready"}), "status")
query.Add(jsonutils.NewString("true"), "admin")
if len(c.Query.Model.Tags) != 0 {
query, err = c.convertTagsQuery(evalContext, query)
if err != nil {
return nil, errors.Wrap(err, "NoDataQueryCondition convertTagsQuery error")
}
}
switch evalContext.Rule.RuleDescription[0].ResType {
case monitor.METRIC_RES_TYPE_HOST:
query.Set("host-type", jsonutils.NewString(hostconsts.TELEGRAF_TAG_KEY_HYPERVISOR))
allResources, err = ListAllResources(&mc_mds.Hosts, query)
case monitor.METRIC_RES_TYPE_GUEST:
allResources, err = ListAllResources(&mc_mds.Servers, query)
case monitor.METRIC_RES_TYPE_RDS:
allResources, err = ListAllResources(&mc_mds.DBInstance, query)
case monitor.METRIC_RES_TYPE_REDIS:
@@ -135,6 +142,29 @@ func (c *NoDataQueryCondition) getOnecloudResources(evalContext *alerting.EvalCo
return allResources, nil
}
func (c *NoDataQueryCondition) convertTagsQuery(evalContext *alerting.EvalContext,
query *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
alertDetails, err := c.GetCommonAlertDetails(evalContext)
if err != nil {
return nil, err
}
for i, _ := range c.Query.Model.Tags {
filterCount := 0
if c.Query.Model.Tags[i].Operator == "=" {
if tag, ok := monitor.MEASUREMENT_TAG_KEYWORD[alertDetails.ResType]; ok {
if c.Query.Model.Tags[i].Key == tag {
query.Set("name", jsonutils.NewString(c.Query.Model.Tags[i].Value))
continue
}
}
}
query.Set(fmt.Sprintf("filter.%d", filterCount),
jsonutils.NewString(fmt.Sprintf("%s.notin(%s)", c.Query.Model.Tags[i].Key, c.Query.Model.Tags[i].Value)))
filterCount++
}
return query, nil
}
func ListAllResources(manager modulebase.Manager, params *jsonutils.JSONDict) ([]jsonutils.JSONObject, error) {
if params == nil {
params = jsonutils.NewDict()

View File

@@ -194,12 +194,10 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
func (c *QueryCondition) NewEvalMatch(context *alerting.EvalContext, series tsdb.TimeSeries,
meta *tsdb.QueryResultMeta, value *float64, valStrArr []string) (*monitor.EvalMatch, error) {
evalMatch := new(monitor.EvalMatch)
alert, err := models.CommonAlertManager.GetAlert(context.Rule.Id)
alertDetails, err := c.GetCommonAlertDetails(context)
if err != nil {
return nil, errors.Wrap(err, "GetAlert to NewEvalMatch error")
}
settings, _ := alert.GetSettings()
alertDetails := alert.GetCommonAlertMetricDetailsFromAlertCondition(c.Index, &settings.Conditions[c.Index])
evalMatch.Metric = fmt.Sprintf("%s.%s", alertDetails.Measurement, alertDetails.Field)
queryKeyInfo := ""
if len(alertDetails.MeasurementDisplayName) > 0 && len(alertDetails.FieldDescription.DisplayName) > 0 {
@@ -226,6 +224,16 @@ func (c *QueryCondition) NewEvalMatch(context *alerting.EvalContext, series tsdb
return evalMatch, nil
}
func (c *QueryCondition) GetCommonAlertDetails(context *alerting.EvalContext) (*monitor.CommonAlertMetricDetails, error) {
alert, err := models.CommonAlertManager.GetAlert(context.Rule.Id)
if err != nil {
return nil, errors.Wrap(err, "GetAlert to NewEvalMatch error")
}
settings, _ := alert.GetSettings()
alertDetails := alert.GetCommonAlertMetricDetailsFromAlertCondition(c.Index, &settings.Conditions[c.Index])
return alertDetails, nil
}
func (c *QueryCondition) jointPointStr(series tsdb.TimeSeries, value string, valStrArr []string) string {
str := ""
for i := 0; i < len(valStrArr); i++ {

View File

@@ -134,6 +134,20 @@ func (s *queryReducer) Reduce(series *tsdb.TimeSeries) (*float64, []string) {
if value > 0 {
allNull = false
}
case "P95":
var values []float64
for _, v := range series.Points {
if v.IsValid() {
allNull = false
values = append(values, v.Value())
}
}
if len(values) >= 1 {
sort.Float64s(values)
length := len(values)
index := math.Floor(float64(length) * 0.95)
value = values[int64(index)]
}
}
if allNull {

View File

@@ -128,7 +128,17 @@ func (c *EvalContext) GetCallbackURLPrefix() string {
return ""
}
url, _ := config.GetString("config", "default", "api_server")
return url + "/alertrecord"
defaultWebUri := "alertrecord"
matchTag := map[string]string{}
if c.Firing {
matchTag = c.EvalMatches[0].Tags
} else {
matchTag = c.AlertOkEvalMatches[0].Tags
}
if uri, ok := matchTag["web_url"]; ok {
defaultWebUri = uri
}
return fmt.Sprintf("%s/%s", url, defaultWebUri)
}
// GetNewState returns the new state from the alert rule evaluation.

View File

@@ -101,18 +101,18 @@ func init() {
// mem
RegistryMetricCreateInput("mem", "Memory", monitor.METRIC_RES_TYPE_HOST,
monitor.METRIC_DATABASE_TELE, 3, []monitor.MetricFieldCreateInput{
monitor.METRIC_DATABASE_TELE, 2, []monitor.MetricFieldCreateInput{
newMetricFieldCreateInput("used_percent", "Used memory rate", monitor.METRIC_UNIT_PERCENT, 1),
newMetricFieldCreateInput("available_percent", "Available memory rate", monitor.METRIC_UNIT_PERCENT, 2),
newMetricFieldCreateInput("used", "Used memory", monitor.METRIC_UNIT_BYTE, 3),
newMetricFieldCreateInput("free", "Free memory", monitor.METRIC_UNIT_BYTE, 4),
newMetricFieldCreateInput("available", "Available memory", monitor.METRIC_UNIT_BYTE, 4),
newMetricFieldCreateInput("active", "The amount of active memory", monitor.METRIC_UNIT_BYTE, 5),
newMetricFieldCreateInput("inactive", "The amount of inactive memory", monitor.METRIC_UNIT_BYTE, 6),
newMetricFieldCreateInput("cached", "Cache memory", monitor.METRIC_UNIT_BYTE, 7),
newMetricFieldCreateInput("buffered", "Buffer memory", monitor.METRIC_UNIT_BYTE, 7),
newMetricFieldCreateInput("slab", "Number of kernel caches", monitor.METRIC_UNIT_BYTE, 8),
newMetricFieldCreateInput("total", "Total memory", monitor.METRIC_UNIT_BYTE, 9),
newMetricFieldCreateInput("available", "Available memory", monitor.METRIC_UNIT_BYTE, 9),
newMetricFieldCreateInput("total", "Total memory", monitor.METRIC_UNIT_BYTE, 10),
})
// net

View File

@@ -336,12 +336,25 @@ func (manager *SAlertRecordManager) getNowAlertingRecord(ctx context.Context, us
query = manager.FilterByOwner(query, userCred, rbacutils.String2Scope(scope))
query = query.GE("created_at", startTime.UTC().Format(timeutils.MysqlTimeFormat))
query = query.Equals("state", monitor.AlertStateAlerting)
sQuery := CommonAlertManager.Query("id").Equals("state", monitor.AlertStateAlerting).IsNull("used_by").SubQuery()
query = query.In("alert_id", sQuery).IsNotNull("res_type").IsNotEmpty("res_type").GroupBy("alert_id")
query = query.IsNotNull("res_type").IsNotEmpty("res_type").Desc("created_at")
alertsQuery := CommonAlertManager.Query("id").Equals("state", monitor.AlertStateAlerting).IsNull("used_by")
alertsQuery = CommonAlertManager.FilterByOwner(alertsQuery, userCred, rbacutils.String2Scope(scope))
alerts := make([]SCommonAlert, 0)
records := make([]SAlertRecord, 0)
err := db.FetchModelObjects(manager, query, &records)
err := db.FetchModelObjects(CommonAlertManager, alertsQuery, &alerts)
if err != nil {
return nil, err
}
for _, alert := range alerts {
tmp := *query
recordModel, err := db.NewModelObject(manager)
if err != nil {
return nil, err
}
if err := (&tmp).Equals("alert_id", alert.GetId()).First(recordModel); err == nil {
records = append(records, *(recordModel.(*SAlertRecord)))
}
}
return records, nil
}

View File

@@ -226,7 +226,7 @@ func (m *SAlertResourceManager) createOrUpdateFromRecord(
if res == nil {
return m.createFromRecord(ctx, userCred, ownerId, drv, record, match)
} else {
return res.updateFromRecord(ctx, userCred, record, match)
return res.updateFromRecord(ctx, userCred, drv, record, match)
}
}
@@ -297,7 +297,8 @@ func (res *SAlertResource) getAttachedAlerts() ([]SCommonAlert, error) {
return alerts, nil
}
func (res *SAlertResource) updateFromRecord(ctx context.Context, userCred mcclient.TokenCredential, record *SAlertRecord, match monitor.EvalMatch) error {
func (res *SAlertResource) updateFromRecord(ctx context.Context, userCred mcclient.TokenCredential,
drv IAlertResourceDriver, record *SAlertRecord, match monitor.EvalMatch) error {
jObj, err := res.GetJointAlert(record.AlertId)
if err != nil {
return errors.Wrapf(err, "get joint alert by id %s", record.AlertId)
@@ -310,6 +311,12 @@ func (res *SAlertResource) updateFromRecord(ctx context.Context, userCred mcclie
if err := jObj.UpdateData(record, &match); err != nil {
return errors.Wrapf(err, "update joint object by matches %v", match)
}
if _, err := db.Update(res, func() error {
res.Type = string(drv.GetType())
return nil
}); err != nil {
return err
}
}
return nil
}

View File

@@ -226,9 +226,7 @@ func (alert *SCommonAlert) CustomizeCreate(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) error {
if err := alert.SAlert.CustomizeCreate(ctx, userCred, ownerId, query, data); err != nil {
return err
}
alert.State = string(monitor.AlertStateUnknown)
input := new(monitor.CommonAlertCreateInput)
if err := data.Unmarshal(input); err != nil {
return err

View File

@@ -191,7 +191,7 @@ func (self *SUnifiedMonitorManager) AllowPerformQuery(ctx context.Context, userC
func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
tmp := jsonutils.DeepCopy(data)
self.handleDataPreSignature(tmp)
self.handleDataPreSignature(ctx, tmp)
if err := ValidateQuerySignature(tmp); err != nil {
return nil, errors.Wrap(err, "ValidateQuerySignature")
}
@@ -235,19 +235,28 @@ func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred m
return jsonutils.Marshal(rtn), nil
}
func (self *SUnifiedMonitorManager) handleDataPreSignature(data jsonutils.JSONObject) {
func (self *SUnifiedMonitorManager) handleDataPreSignature(ctx context.Context, data jsonutils.JSONObject) {
scope, _ := data.GetString("scope")
isIdentityName, _ := data.Bool("identity_name")
switch scope {
case "system":
case "domain":
domain, err := data.GetString("project_domain")
if err == nil {
domainObj, _ := db.DefaultDomainFetcher(ctx, domain)
if isIdentityName {
domain = domainObj.Name
}
data.(*jsonutils.JSONDict).Remove("project_domain")
data.(*jsonutils.JSONDict).Set("domain_id", jsonutils.NewString(domain))
}
default:
project, err := data.GetString("project")
if err == nil {
tenant, _ := db.DefaultProjectFetcher(ctx, project)
if isIdentityName {
project = tenant.Name
}
data.(*jsonutils.JSONDict).Remove("project")
data.(*jsonutils.JSONDict).Set("project_id", jsonutils.NewString(project))
}

View File

@@ -54,6 +54,8 @@ const (
AWS_GLOBAL_ARN_PREFIX = "arn:aws:iam::aws:policy/"
AWS_CHINA_ARN_PREFIX = "arn:aws-cn:iam::aws:policy/"
DEFAULT_S3_REGION_ID = "us-east-1"
)
var (
@@ -304,6 +306,11 @@ func (client *SAwsClient) fetchBuckets() error {
}
location := *output.LocationConstraint
if len(location) == 0 {
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html
// Buckets in Region us-east-1 have a LocationConstraint of null.
location = DEFAULT_S3_REGION_ID
}
region, err := client.getIRegionByRegionId(location)
if err != nil {
log.Errorf("client.getIRegionByRegionId %s fail %s", location, err)

View File

@@ -992,8 +992,11 @@ func (region *SRegion) CreateIBucket(name string, storageClassStr string, acl st
}
input := &s3.CreateBucketInput{}
input.SetBucket(name)
input.CreateBucketConfiguration = &s3.CreateBucketConfiguration{}
input.CreateBucketConfiguration.SetLocationConstraint(region.GetId())
if region.GetId() != DEFAULT_S3_REGION_ID {
location := region.GetId()
input.CreateBucketConfiguration = &s3.CreateBucketConfiguration{}
input.CreateBucketConfiguration.SetLocationConstraint(location)
}
_, err = s3cli.CreateBucket(input)
if err != nil {
return errors.Wrap(err, "CreateBucket")

View File

@@ -142,7 +142,7 @@ func (self *SAzureClient) getClient(resource TAzureResource) (*autorest.Client,
return nil, errors.Wrapf(err, "azureenv.EnvironmentFromName(%s)", self.envName)
}
httpClient := self.cpcfg.HttpClient()
httpClient := self.cpcfg.AdaptiveTimeoutHttpClient()
client.Sender = httpClient
self.env = env
@@ -200,16 +200,20 @@ func (self *SAzureClient) jsonRequest(method, path string, body jsonutils.JSONOb
if ae, ok := err.(*AzureResponseError); ok {
switch ae.AzureError.Code {
case "SubscriptionNotRegistered":
err = self.ServiceRegister("Microsoft.Network")
if err != nil {
return nil, errors.Wrapf(err, "self.registerService(Microsoft.Network)")
service := self.getService(path)
if len(service) == 0 {
return nil, err
}
re := self.ServiceRegister("Microsoft.Network")
if re != nil {
return nil, errors.Wrapf(re, "self.registerService(Microsoft.Network)")
}
continue
case "MissingSubscriptionRegistration":
for _, serviceType := range ae.AzureError.Details {
err = self.ServiceRegister(serviceType.Target)
re := self.ServiceRegister(serviceType.Target)
if err != nil {
return nil, errors.Wrapf(err, "self.registerService(%s)", serviceType.Target)
return nil, errors.Wrapf(re, "self.registerService(%s)", serviceType.Target)
}
}
continue
@@ -367,6 +371,25 @@ func (self *SAzureClient) list(resource string, params url.Values, retVal interf
return jsonutils.Update(retVal, result)
}
func (self *SAzureClient) getService(path string) string {
for _, service := range []string{
"microsoft.compute",
"microsoft.classiccompute",
"microsoft.network",
"microsoft.classicnetwork",
"microsoft.storage",
"microsoft.classicstorage",
"microsoft.billing",
"microsoft.insights",
"microsoft.authorization",
} {
if strings.Contains(strings.ToLower(path), service) {
return service
}
}
return ""
}
func (self *SAzureClient) _apiVersion(resource string, params url.Values) string {
version := params.Get("api-version")
if len(version) > 0 {

View File

@@ -31,6 +31,7 @@ import (
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud"
"yunion.io/x/onecloud/pkg/util/billing"
"yunion.io/x/onecloud/pkg/util/version"
)
const (
@@ -155,6 +156,23 @@ type VirtualMachineProperties struct {
VmId string `json:"vmId,omitempty"`
}
type SExtensionResourceProperties struct {
AutoUpgradeMinorVersion bool
ProvisioningState string
Publisher string
Type string
TypeHandlerVersion string
}
type SExtensionResource struct {
Id string
Name string
Type string
Location string
Properties SExtensionResourceProperties
}
type SInstance struct {
multicloud.SInstanceBase
host *SHost
@@ -166,6 +184,8 @@ type SInstance struct {
Location string
vmSize *SVMSize
Tags map[string]string
Resources []SExtensionResource
}
func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) {
@@ -500,15 +520,22 @@ func (region *SRegion) resetOvsEnv(instanceId string) error {
func (region *SRegion) deleteExtension(instanceId, extensionName string) error {
return region.del(fmt.Sprintf("%s/extensions/%s", instanceId, extensionName))
}
func (region *SRegion) resetLoginInfo(osType, instanceId string, setting map[string]string) error {
func (region *SRegion) resetLoginInfo(osType, instanceId string, setting map[string]interface{}) error {
// https://github.com/Azure/azure-linux-extensions/blob/master/VMAccess/README.md
handlerVersion := "1.5"
properties := map[string]interface{}{
"Publisher": "Microsoft.OSTCExtensions",
"Type": "VMAccessForLinux",
"TypeHandlerVersion": "1.4",
"Settings": setting,
"TypeHandlerVersion": handlerVersion,
"Settings": map[string]string{},
"protectedSettings": setting,
"autoUpgradeMinorVersion": true,
}
if osType == osprofile.OS_TYPE_WINDOWS {
properties["TypeHandlerVersion"] = "2.0"
// https://github.com/Azure/azure-cli/blob/dev/src/azure-cli/azure/cli/command_modules/vm/custom.py
handlerVersion = "2.4"
properties["TypeHandlerVersion"] = handlerVersion
properties["Publisher"] = "Microsoft.Compute"
properties["Type"] = "VMAccessAgent"
}
@@ -516,8 +543,20 @@ func (region *SRegion) resetLoginInfo(osType, instanceId string, setting map[str
"Location": region.Name,
"Properties": properties,
}
instance, err := region.GetInstance(instanceId)
if err != nil {
return errors.Wrapf(err, "GetInstance(%s)", instanceId)
}
for _, extension := range instance.Resources {
if extension.Name == "enablevmaccess" {
if version.GT(extension.Properties.TypeHandlerVersion, handlerVersion) {
properties["TypeHandlerVersion"] = extension.Properties.TypeHandlerVersion
break
}
}
}
resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId)
_, err := region.put(resource, jsonutils.Marshal(params))
_, err = region.put(resource, jsonutils.Marshal(params))
if err != nil {
switch osType {
case osprofile.OS_TYPE_WINDOWS:
@@ -536,11 +575,39 @@ func (region *SRegion) resetLoginInfo(osType, instanceId string, setting map[str
return err
}
}
err = cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
instance, err := region.GetInstance(instanceId)
if err != nil {
return false, errors.Wrapf(err, "GetInstance(%s)", instanceId)
}
for _, extension := range instance.Resources {
if extension.Name == "enablevmaccess" {
if extension.Properties.ProvisioningState == "Succeeded" {
return true, nil
}
log.Debugf("enablevmaccess status %s expect Succeeded", extension.Properties.ProvisioningState)
if extension.Properties.ProvisioningState == "Failed" {
if instance.Properties.InstanceView != nil {
for _, info := range instance.Properties.InstanceView.Extensions {
if info.Name == "enablevmaccess" && len(info.Statuses) > 0 {
return false, fmt.Errorf("details: %s", jsonutils.Marshal(info.Statuses))
}
}
}
return false, fmt.Errorf("reset passwod failed")
}
}
}
return false, nil
})
if err != nil {
return errors.Wrapf(err, "wait for enablevmaccess error: %v", err)
}
return nil
}
func (region *SRegion) resetPublicKey(osType, instanceId string, username, publicKey string) error {
setting := map[string]string{
setting := map[string]interface{}{
"username": username,
"ssh_key": publicKey,
}
@@ -548,7 +615,7 @@ func (region *SRegion) resetPublicKey(osType, instanceId string, username, publi
}
func (region *SRegion) resetPassword(osType, instanceId, username, password string) error {
setting := map[string]string{
setting := map[string]interface{}{
"username": username,
"password": password,
}

View File

@@ -777,6 +777,20 @@ func (self *SInstance) DeleteVM(ctx context.Context) error {
return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId)
}
if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID { // 预付费的需要删除两次
cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) {
err = self.Refresh()
if err != nil {
log.Warningf("refresh instance %s(%s) error: %v", self.InstanceName, self.InstanceId, err)
return false, nil
}
// 需要等待第一次删除后状态变为SHUTDOWN才可以进行第二次删除否则会报:
// Code=OperationDenied.InstanceOperationInProgress, Message=该实例`['ins-mxqturgj']`
if self.InstanceState == "SHUTDOWN" {
return true, nil
}
log.Debugf("wait %s(%s) status be SHUTDOWN, current is %s", self.InstanceName, self.InstanceId, self.InstanceState)
return false, nil
})
err := self.host.zone.region.DeleteVM(self.InstanceId)
if err != nil {
return errors.Wrapf(err, "region.DeleteVM(%s)", self.InstanceId)

View File

@@ -28,19 +28,37 @@ type INotifyService interface {
StopAll()
UpdateServices(ctx context.Context, userCred mcclient.TokenCredential, isStart bool)
RestartService(ctx context.Context, config SConfig, serviceName string)
Send(ctx context.Context, contactType, contact, topic, msg, priority string) error
Send(ctx context.Context, p SSendParams) error
ContactByMobile(ctx context.Context, mobile, serviceName string) (string, error)
BatchSend(ctx context.Context, contacts []string, contactType, topic, message, priority string) ([]*apis.FailedRecord, error)
BatchSend(ctx context.Context, p SBatchSendParams) ([]*apis.FailedRecord, error)
ValidateConfig(ctx context.Context, cType string, configs map[string]string) (isValid bool, message string, err error)
}
type SSendParams struct {
ContactType string
Contact string
Topic string
Message string
Priority string
Lang string
}
type SBatchSendParams struct {
ContactType string
Contacts []string
Topic string
Message string
Priority string
Lang string
}
type IServiceConfigStore interface {
GetConfig(serviceName string) (SConfig, error)
SetConfig(serviceName string, config SConfig) error
}
type ITemplateStore interface {
NotifyFilter(contactType, topic, msg string) (params apis.SendParams, err error)
NotifyFilter(contactType, topic, msg, lang string) (params apis.SendParams, err error)
}
type SConfig map[string]string

View File

@@ -128,7 +128,11 @@ func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred
if len(input.Topic) < 10 {
length = len(input.Topic)
}
input.Name = fmt.Sprintf("%s-%s-%s", input.Topic[:length], input.ContactType, nowStr)
name := fmt.Sprintf("%s-%s-%s", input.Topic[:length], input.ContactType, nowStr)
input.Name, err = db.GenerateName(nm, ownerId, name)
if err != nil {
return input, errors.Wrapf(err, "unable to generate name for %s", name)
}
return input, nil
}
@@ -227,7 +231,7 @@ func (n *SNotification) ReceiveDetails() ([]api.ReceiveDetail, error) {
func (n *SNotification) getMoreDetails(ctx context.Context, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) {
// get title adn content
p, err := TemplateManager.NotifyFilter(n.ContactType, n.Topic, n.Message)
p, err := TemplateManager.NotifyFilter(n.ContactType, n.Topic, n.Message, getTemplateLangFromCtx(ctx))
if err != nil {
return out, err
}

View File

@@ -17,8 +17,11 @@ package models
import (
"context"
"database/sql"
"regexp"
"time"
"golang.org/x/text/language"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
@@ -96,6 +99,7 @@ type SReceiver struct {
Email string `width:"64" nullable:"false" create:"optional" update:"user" get:"user" list:"user"`
Mobile string `width:"16" nullable:"false" create:"optional" update:"user" get:"user" list:"user"`
Lang string `width:"8" charset:"ascii" nullable:"false" list:"user" update:"user"`
// swagger:ignore
EnabledEmail tristate.TriState `nullable:"false" default:"false" update:"user"`
@@ -276,12 +280,14 @@ func (rm *SReceiverManager) ValidateCreateData(ctx context.Context, userCred mcc
return input, httperrors.NewInputParameterError("invalid email")
}
// validate mobile
if ok := regutils.MatchMobile(input.Mobile); len(input.Mobile) > 0 && !ok {
if ok := LaxMobileRegexp.MatchString(input.Mobile); len(input.Mobile) > 0 && !ok {
return input, httperrors.NewInputParameterError("invalid mobile")
}
return input, nil
}
var LaxMobileRegexp = regexp.MustCompile(`[0-9]{6,14}`)
func (r *SReceiver) IsEnabledContactType(ct string) (bool, error) {
if utils.IsInStringArray(ct, AllOkContactTypes) {
return true, nil
@@ -678,7 +684,7 @@ func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.To
return input, httperrors.NewInputParameterError("invalid email")
}
// validate mobile
if ok := len(input.Mobile) == 0 || regutils.MatchMobile(input.Mobile); !ok {
if ok := len(input.Mobile) == 0 || LaxMobileRegexp.MatchString(input.Mobile); !ok {
return input, httperrors.NewInputParameterError("invalid mobile")
}
return input, nil
@@ -907,6 +913,51 @@ func (r *SReceiver) PerformDisable(ctx context.Context, userCred mcclient.TokenC
return nil, nil
}
func (r *SReceiver) Sync(ctx context.Context) error {
session := auth.GetAdminSessionWithInternal(ctx, "", "")
params := jsonutils.NewDict()
params.Set("scope", jsonutils.NewString("system"))
params.Set("system", jsonutils.JSONTrue)
data, err := modules.UsersV3.GetById(session, r.Id, params)
if err != nil {
jerr := err.(*httputils.JSONClientError)
if jerr.Code == 404 {
err := r.Delete(ctx, session.GetToken())
if err != nil {
return errors.Wrapf(err, "unable to delete receiver %s", r.Id)
}
return errors.Wrapf(errors.ErrNotFound, "no such receiver %s", r.Id)
}
return err
}
uname, _ := data.GetString("name")
domainId, _ := data.GetString("domain_id")
lang, _ := data.GetString("lang")
_, err = db.Update(r, func() error {
r.Name = uname
r.DomainId = domainId
r.Lang = lang
return nil
})
return errors.Wrap(err, "unable to update")
}
func (r *SReceiver) GetTemplateLang(ctx context.Context) (string, error) {
if len(r.Lang) == 0 {
err := r.Sync(ctx)
if err != nil {
return "", err
}
}
log.Infof("lang: %s", r.Lang)
lang, err := language.Parse(r.Lang)
if err != nil {
return "", errors.Wrapf(err, "unable to prase language %q", r.Lang)
}
tLang := notifyclientI18nTable.LookupByLang(lang, tempalteLang)
return tLang, nil
}
// Implemente interface EventHandler
func (rm *SReceiverManager) OnAdd(obj *jsonutils.JSONDict) {
// do nothing
@@ -926,12 +977,14 @@ func (rm *SReceiverManager) OnUpdate(oldObj, newObj *jsonutils.JSONDict) {
receiver := &receivers[0]
uname, _ := newObj.GetString("name")
domainId, _ := newObj.GetString("domain_id")
if receiver.Name == uname && receiver.DomainId == domainId {
lang, _ := newObj.GetString("lang")
if receiver.Name == uname && receiver.DomainId == domainId && receiver.Lang == lang {
return
}
_, err = db.Update(receiver, func() error {
receiver.Name = uname
receiver.DomainId = domainId
receiver.Lang = lang
return nil
})
if err != nil {

View File

@@ -85,6 +85,7 @@ func (rn *SReceiverNotification) Receiver() (*SReceiver, error) {
if err != nil {
return nil, err
}
receiver.SetModelManager(ReceiverManager, &receiver)
return &receiver, nil
}

View File

@@ -33,6 +33,7 @@ import (
api "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/i18n"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
@@ -72,6 +73,7 @@ type STemplate struct {
// title | content | remote
TemplateType string `width:"10" nullable:"false" create:"required" update:"user" list:"user"`
Content string `length:"text" nullable:"false" create:"required" get:"user" list:"user" update:"user"`
Lang string `width:"8" charset:"ascii" nullable:"false" list:"user" update:"user"`
Example string `nullable:"false" created:"required" get:"user" list:"user" update:"user"`
}
@@ -88,32 +90,35 @@ func (tm *STemplateManager) defaultTemplate() ([]STemplate, error) {
templates := make([]STemplate, 0, 4)
for _, templateType := range []string{"title", "content"} {
contactType, topic := CONTACTTYPE_ALL, ""
titleTemplatePath := fmt.Sprintf("%s/%s", templatePath, templateType)
files, err := ioutil.ReadDir(titleTemplatePath)
if err != nil {
return templates, errors.Wrapf(err, "Read Dir '%s'", titleTemplatePath)
}
for _, file := range files {
if file.IsDir() {
continue
}
spliteName := strings.Split(file.Name(), ".")
topic = spliteName[0]
if len(spliteName) > 1 {
contactType = spliteName[1]
}
fullPath := filepath.Join(titleTemplatePath, file.Name())
content, err := ioutil.ReadFile(fullPath)
for _, lang := range []string{api.TEMPLATE_LANG_CN, api.TEMPLATE_LANG_EN} {
contactType, topic := CONTACTTYPE_ALL, ""
titleTemplatePath := fmt.Sprintf("%s/%s@%s", templatePath, templateType, lang)
files, err := ioutil.ReadDir(titleTemplatePath)
if err != nil {
return templates, err
return templates, errors.Wrapf(err, "Read Dir '%s'", titleTemplatePath)
}
for _, file := range files {
if file.IsDir() {
continue
}
spliteName := strings.Split(file.Name(), ".")
topic = spliteName[0]
if len(spliteName) > 1 {
contactType = spliteName[1]
}
fullPath := filepath.Join(titleTemplatePath, file.Name())
content, err := ioutil.ReadFile(fullPath)
if err != nil {
return templates, err
}
templates = append(templates, STemplate{
ContactType: contactType,
Topic: topic,
Lang: lang,
TemplateType: templateType,
Content: string(content),
})
}
templates = append(templates, STemplate{
ContactType: contactType,
Topic: topic,
TemplateType: templateType,
Content: string(content),
})
}
}
return templates, nil
@@ -144,15 +149,43 @@ var (
ForceInitType = []string{
api.EMAIL,
}
notifyclientI18nTable = i18n.Table{}
defaultLang = api.TEMPLATE_LANG_CN
tempalteLang = "lang"
)
func init() {
notifyclientI18nTable.Set(tempalteLang, i18n.NewTableEntry().EN(api.TEMPLATE_LANG_EN).CN(api.TEMPLATE_LANG_CN))
}
func getTemplateLangFromCtx(ctx context.Context) string {
return notifyclientI18nTable.Lookup(ctx, tempalteLang)
}
func (tm *STemplateManager) InitializeData() error {
// init lang
q := tm.Query().IsEmpty("lang")
var noLangTemplates []STemplate
err := db.FetchModelObjects(tm, q, &noLangTemplates)
if err != nil {
return errors.Wrap(err, "unable to fetch templates")
}
for i := range noLangTemplates {
t := &noLangTemplates[i]
_, err := db.Update(t, func() error {
t.Lang = defaultLang
return nil
})
if err != nil {
return err
}
}
templates, err := tm.defaultTemplate()
if err != nil {
return err
}
for _, template := range templates {
q := tm.Query().Equals("contact_type", template.ContactType).Equals("topic", template.Topic).Equals("template_type", template.TemplateType)
q := tm.Query().Equals("contact_type", template.ContactType).Equals("topic", template.Topic).Equals("template_type", template.TemplateType).Equals("lang", template.Lang)
count, _ := q.CountWithError()
if count > 0 && !utils.IsInStringArray(template.ContactType, ForceInitType) {
continue
@@ -195,10 +228,15 @@ func (tm *STemplateManager) InitializeData() error {
// NotifyFilter will return the title and content generated by corresponding template.
// Local cache about common template will be considered in case of performance issues.
func (tm *STemplateManager) NotifyFilter(contactType, topic, msg string) (params apis.SendParams, err error) {
func (tm *STemplateManager) NotifyFilter(contactType, topic, msg, lang string) (params apis.SendParams, err error) {
params.Topic = topic
if len(lang) == 0 {
params.Title = topic
params.Message = msg
return
}
templates := make([]STemplate, 0, 3)
q := tm.Query().Equals("topic", strings.ToUpper(topic)).In("contact_type", []string{CONTACTTYPE_ALL, contactType})
q := tm.Query().Equals("topic", strings.ToUpper(topic)).Equals("lang", lang).In("contact_type", []string{CONTACTTYPE_ALL, contactType})
err = db.FetchModelObjects(tm, q, &templates)
if errors.Cause(err) == sql.ErrNoRows || len(templates) == 0 {
// no such template, return as is

View File

@@ -31,7 +31,7 @@ type NotifyOption struct {
InitNotificationScope int `help:"initialize data of notification with in InitNotificationScope hours" default:"100"`
MaxSyncNotification int `help:"The max number of notification sync from old data source" default:"1000"`
VerifyExpireInterval int `help:"expire interval of verify message; minutes" default:"5"`
VerifyExpireInterval int `help:"expire interval of verify message; minutes" default:"2"`
VerifyValidInterval int `help:"valid interval of verify message; miniutes" default:"20"`
}

View File

@@ -32,6 +32,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/notify"
notifyv2 "yunion.io/x/onecloud/pkg/notify"
"yunion.io/x/onecloud/pkg/notify/models"
"yunion.io/x/onecloud/pkg/notify/rpc/apis"
@@ -107,22 +108,22 @@ func (self *SRpcService) StopAll() {
}
// Send call the corresponding rpc server to send messager.
func (self *SRpcService) Send(ctx context.Context, contactType, contact, topic, msg, priority string) error {
func (self *SRpcService) Send(ctx context.Context, p notify.SSendParams) error {
args, err := self.templateStore.NotifyFilter(contactType, topic, msg)
args, err := self.templateStore.NotifyFilter(p.ContactType, p.Topic, p.Message, p.Lang)
if err != nil {
return errors.Wrap(err, "templateStore.NotifyFilter")
}
args.Contact = contact
args.Priority = priority
args.Contact = p.Contact
args.Priority = p.Priority
f := func(service *apis.SendNotificationClient) (interface{}, error) {
log.Debugf("send one")
return service.Send(ctx, &args)
}
_, err = self.execute(ctx, f, contactType)
_, err = self.execute(ctx, f, p.ContactType)
if err != nil {
s, ok := status.FromError(err)
if !ok {
@@ -133,14 +134,14 @@ func (self *SRpcService) Send(ctx context.Context, contactType, contact, topic,
return nil
}
func (self *SRpcService) BatchSend(ctx context.Context, contacts []string, contactType, topic, message, priority string) ([]*apis.FailedRecord, error) {
args, err := self.templateStore.NotifyFilter(contactType, topic, message)
func (self *SRpcService) BatchSend(ctx context.Context, p notify.SBatchSendParams) ([]*apis.FailedRecord, error) {
args, err := self.templateStore.NotifyFilter(p.ContactType, p.Topic, p.Message, p.Lang)
if err != nil {
return nil, errors.Wrap(err, "templateStore.NotifyFilter")
}
batchSendParams := apis.BatchSendParams{
Contacts: contacts,
Contacts: p.Contacts,
Title: args.Title,
Message: args.Message,
Priority: args.Priority,
@@ -151,7 +152,7 @@ func (self *SRpcService) BatchSend(ctx context.Context, contacts []string, conta
return service.BatchSend(ctx, &batchSendParams)
}
ret, err := self.execute(ctx, f, contactType)
ret, err := self.execute(ctx, f, p.ContactType)
if err != nil {
s, ok := status.FromError(err)
if !ok {

View File

@@ -12,6 +12,7 @@ import (
apis "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/notify"
"yunion.io/x/onecloud/pkg/notify/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
@@ -71,6 +72,8 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone
// build contactMap
contactMap := make(map[string]*models.SReceiverNotification)
contactMapEn := make(map[string]*models.SReceiverNotification)
contactmapCn := make(map[string]*models.SReceiverNotification)
for i := range rnsWithReceiver {
if len(rnsWithReceiver[i].ReceiverID) == 0 {
contactMap[rnsWithReceiver[i].Contact] = rnsWithReceiver[i]
@@ -114,44 +117,73 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone
sendFail(rnsWithReceiver[i], reason)
continue
}
contactMap[contact] = rnsWithReceiver[i]
lang, err := receiver.GetTemplateLang(ctx)
if err != nil {
reason := fmt.Sprintf("fail to GetTemplateLang: %s", err.Error())
sendFail(rnsWithReceiver[i], reason)
continue
}
switch lang {
case "":
contactMap[contact] = rnsWithReceiver[i]
case apis.TEMPLATE_LANG_EN:
contactMapEn[contact] = rnsWithReceiver[i]
case apis.TEMPLATE_LANG_CN:
contactmapCn[contact] = rnsWithReceiver[i]
}
}
for i := range rnsWithoutReceiver {
contactMap[rnsWithoutReceiver[i].Contact] = rnsWithoutReceiver[i]
}
// set status before send
now := time.Now()
contacts := make([]string, 0, len(contactMap))
for c, rn := range contactMap {
rn.BeforeSend(ctx, now)
contacts = append(contacts, c)
}
// send
ret, err := models.NotifyService.BatchSend(ctx, contacts, notification.ContactType, notification.Topic, notification.Message, notification.Priority)
if err != nil {
for _, rn := range contactMap {
rn.AfterSend(ctx, false, err.Error())
var contactLen int
for lang, contactMap := range map[string]map[string]*models.SReceiverNotification{
"": contactMap,
apis.TEMPLATE_LANG_CN: contactmapCn,
apis.TEMPLATE_LANG_EN: contactMapEn,
} {
if len(contactMap) == 0 {
continue
}
// set status before send
now := time.Now()
contacts := make([]string, 0, len(contactMap))
for c, rn := range contactMap {
rn.BeforeSend(ctx, now)
contacts = append(contacts, c)
}
failedRecord = append(failedRecord, fmt.Sprintf("others: %s", err.Error()))
self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true)
return
}
// check result
for _, fd := range ret {
rn := contactMap[fd.Contact]
rn.AfterSend(ctx, false, fd.Reason)
failedRecord = append(failedRecord, fmt.Sprintf("%s: %s", rn.ReceiverID, fd.Reason))
delete(contactMap, fd.Contact)
contactLen += len(contacts)
p := notify.SBatchSendParams{
Contacts: contacts,
ContactType: notification.ContactType,
Topic: notification.Topic,
Message: notification.Message,
Priority: notification.Priority,
Lang: lang,
}
// send
fds, err := models.NotifyService.BatchSend(ctx, p)
if err != nil {
for _, rn := range contactMap {
sendFail(rn, err.Error())
}
continue
}
// check result
for _, fd := range fds {
rn := contactMap[fd.Contact]
sendFail(rn, fd.Reason)
delete(contactMap, fd.Contact)
}
// after send for successful notify
for _, rn := range contactMap {
rn.AfterSend(ctx, true, "")
}
}
// after send for successful notify
for _, rn := range contactMap {
rn.AfterSend(ctx, true, "")
}
if len(failedRecord) > 0 && len(failedRecord) == len(contacts) {
if len(failedRecord) > 0 && len(failedRecord) == contactLen {
self.taskFailed(ctx, notification, strings.Join(failedRecord, "; "), true)
return
}

View File

@@ -10,6 +10,7 @@ import (
api "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/notify"
"yunion.io/x/onecloud/pkg/notify/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
@@ -66,7 +67,19 @@ func (self *VerificationSendTask) OnInit(ctx context.Context, obj db.IStandalone
default:
// no way
}
err = models.NotifyService.Send(ctx, contactType, contact, "verify", message, "")
tLang, err := receiver.GetTemplateLang(ctx)
if err != nil {
self.taskFailed(ctx, receiver, fmt.Sprintf("unable to GetTemplateLang for receiver %q: %v", receiver.Id, err))
}
sendP := notify.SSendParams{
ContactType: contactType,
Contact: contact,
Message: message,
Topic: "verify",
Priority: "",
Lang: tLang,
}
err = models.NotifyService.Send(ctx, sendP)
if err != nil {
self.taskFailed(ctx, receiver, err.Error())
return