fix(region): vender update for qcloud lb listener

This commit is contained in:
ioito
2023-01-12 18:24:50 +08:00
parent 4729919fb9
commit 9e9d794eef
8 changed files with 166 additions and 112 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ require (
k8s.io/client-go v0.19.3
k8s.io/cluster-bootstrap v0.19.3
moul.io/http2curl/v2 v2.3.0
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230110105742-0052589685ec
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230112102153-f604caf2c2b7
yunion.io/x/executor v0.0.0-20211018100936-39a2cd966656
yunion.io/x/jsonutils v1.0.1-0.20220819091305-3bab322ab4fd
yunion.io/x/log v1.0.0
+2 -2
View File
@@ -1158,8 +1158,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230110105742-0052589685ec h1:p9OjNhquM20n3J39TY9XMx1LL2xJPKaPtHkrrP/1nAo=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230110105742-0052589685ec/go.mod h1:yEMFtIKPyIdIfPzsw7oTcH41JXc8G5StUAQw3ZoGj5I=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230112102153-f604caf2c2b7 h1:BTxx46pcb26zuu+qH3dejDn18y9MOGKF6C931iGqLVA=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230112102153-f604caf2c2b7/go.mod h1:yEMFtIKPyIdIfPzsw7oTcH41JXc8G5StUAQw3ZoGj5I=
yunion.io/x/executor v0.0.0-20211018100936-39a2cd966656 h1:0zlZD5uhZoIHgLVAWCz2aHaYk2ZrNsACCYD7R6EIBII=
yunion.io/x/executor v0.0.0-20211018100936-39a2cd966656/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
+1
View File
@@ -251,6 +251,7 @@ var LB_HEALTH_CHECK_TYPES = []string{
LB_HEALTH_CHECK_TCP,
LB_HEALTH_CHECK_UDP,
LB_HEALTH_CHECK_HTTP,
LB_HEALTH_CHECK_PING,
}
var LB_HEALTH_CHECK_TYPES_TCP = choices.NewChoices(
+40 -28
View File
@@ -747,12 +747,12 @@ func (man *SLoadbalancerListenerManager) SyncLoadbalancerListeners(ctx context.C
localListeners := []SLoadbalancerListener{}
remoteListeners := []cloudprovider.ICloudLoadbalancerListener{}
syncResult := compare.SyncResult{}
result := compare.SyncResult{}
dbListeners, err := man.getLoadbalancerListenersByLoadbalancer(lb)
if err != nil {
syncResult.Error(err)
return nil, nil, syncResult
result.Error(err)
return nil, nil, result
}
removed := []SLoadbalancerListener{}
@@ -762,41 +762,39 @@ func (man *SLoadbalancerListenerManager) SyncLoadbalancerListeners(ctx context.C
err = compare.CompareSets(dbListeners, listeners, &removed, &commondb, &commonext, &added)
if err != nil {
syncResult.Error(err)
return nil, nil, syncResult
result.Error(err)
return nil, nil, result
}
for i := 0; i < len(removed); i++ {
err = removed[i].syncRemoveCloudLoadbalancerListener(ctx, userCred)
if err != nil {
syncResult.DeleteError(err)
result.DeleteError(err)
} else {
syncResult.Delete()
result.Delete()
}
}
for i := 0; i < len(commondb); i++ {
err = commondb[i].SyncWithCloudLoadbalancerListener(ctx, userCred, lb, commonext[i], syncOwnerId, provider)
if err != nil {
syncResult.UpdateError(err)
} else {
syncMetadata(ctx, userCred, &commondb[i], commonext[i])
localListeners = append(localListeners, commondb[i])
remoteListeners = append(remoteListeners, commonext[i])
syncResult.Update()
result.UpdateError(err)
continue
}
localListeners = append(localListeners, commondb[i])
remoteListeners = append(remoteListeners, commonext[i])
result.Update()
}
for i := 0; i < len(added); i++ {
new, err := man.newFromCloudLoadbalancerListener(ctx, userCred, lb, added[i], syncOwnerId, provider)
if err != nil {
syncResult.AddError(err)
} else {
syncMetadata(ctx, userCred, new, added[i])
localListeners = append(localListeners, *new)
remoteListeners = append(remoteListeners, added[i])
syncResult.Add()
result.AddError(err)
continue
}
localListeners = append(localListeners, *new)
remoteListeners = append(remoteListeners, added[i])
result.Add()
}
return localListeners, remoteListeners, syncResult
return localListeners, remoteListeners, result
}
func (lblis *SLoadbalancerListener) constructFieldsFromCloudListener(userCred mcclient.TokenCredential, lb *SLoadbalancer, extListener cloudprovider.ICloudLoadbalancerListener) {
@@ -902,12 +900,24 @@ func (lblis *SLoadbalancerListener) constructFieldsFromCloudListener(userCred mc
}
func (lblis *SLoadbalancerListener) updateCachedLoadbalancerBackendGroupAssociate(ctx context.Context, extListener cloudprovider.ICloudLoadbalancerListener, managerId string) error {
exteralLbbgId := extListener.GetBackendGroupId()
if len(exteralLbbgId) == 0 {
func (lblis *SLoadbalancerListener) updateBackendGroupId(ctx context.Context, ext cloudprovider.ICloudLoadbalancerListener, managerId string) error {
extId := ext.GetBackendGroupId()
if len(extId) == 0 {
return nil
}
q := LoadbalancerBackendGroupManager.Query().Equals("external_id", extId).Equals("loadbalancer_id", lblis.LoadbalancerId)
groups := []SLoadbalancerBackendGroup{}
err := db.FetchModelObjects(LoadbalancerBackendGroupManager, q, &groups)
if err != nil {
return errors.Wrapf(err, "db.FetchModelObjects")
}
if len(groups) == 1 {
_, err := db.Update(lblis, func() error {
lblis.BackendGroupId = groups[0].Id
return nil
})
return err
}
return nil
}
@@ -934,6 +944,7 @@ func (lblis *SLoadbalancerListener) SyncWithCloudLoadbalancerListener(ctx contex
if err != nil {
return err
}
syncMetadata(ctx, userCred, lblis, extListener)
if len(diff) > 0 {
notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
@@ -942,9 +953,9 @@ func (lblis *SLoadbalancerListener) SyncWithCloudLoadbalancerListener(ctx contex
})
}
err = lblis.updateCachedLoadbalancerBackendGroupAssociate(ctx, extListener, lb.ManagerId)
err = lblis.updateBackendGroupId(ctx, extListener, lb.ManagerId)
if err != nil {
return errors.Wrap(err, "LoadbalancerListener.SyncWithCloudLoadbalancerListener")
return errors.Wrap(err, "updateBackendGroupId")
}
db.OpsLog.LogSyncUpdate(lblis, diff, userCred)
@@ -976,10 +987,11 @@ func (man *SLoadbalancerListenerManager) newFromCloudLoadbalancerListener(ctx co
if err != nil {
return nil, errors.Wrapf(err, "Insert")
}
syncMetadata(ctx, userCred, lblis, extListener)
err = lblis.updateCachedLoadbalancerBackendGroupAssociate(ctx, extListener, lb.ManagerId)
err = lblis.updateBackendGroupId(ctx, extListener, lb.ManagerId)
if err != nil {
return nil, errors.Wrap(err, "LoadbalancerListener.newFromCloudLoadbalancerListener")
return nil, errors.Wrap(err, "updateBackendGroupId")
}
notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
+1 -1
View File
@@ -1439,7 +1439,7 @@ sigs.k8s.io/structured-merge-diff/v4/value
# sigs.k8s.io/yaml v1.2.0
## explicit; go 1.12
sigs.k8s.io/yaml
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230110105742-0052589685ec
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20230112102153-f604caf2c2b7
## explicit; go 1.18
yunion.io/x/cloudmux/pkg/apis
yunion.io/x/cloudmux/pkg/apis/billing
-55
View File
@@ -364,61 +364,6 @@ func (self *SRegion) DeleteLoadbalancer(lbid string) (string, error) {
return resp.GetString("RequestId")
}
/*
/*
https://cloud.tencent.com/document/product/214/30693
SNI 特性是什么??
*/
func (self *SRegion) CreateLoadbalancerListener(lbId string, opts *cloudprovider.SLoadbalancerListenerCreateOptions) (string, error) {
params := map[string]string{
"LoadBalancerId": lbId,
"Ports.0": fmt.Sprintf("%d", opts.ListenerPort),
"Protocol": opts.ListenerType,
"ListenerNames.0": opts.Name,
}
switch opts.Scheduler {
case api.LB_SCHEDULER_WRR:
params["Scheduler"] = "WRR"
case api.LB_SCHEDULER_WLC:
params["Scheduler"] = "LEAST_CONN"
case api.LB_SCHEDULER_SCH:
params["Scheduler"] = "IP_HASH"
}
switch opts.ListenerType {
case api.LB_LISTENER_TYPE_TCP:
if opts.StickySession == api.LB_STICKY_SESSION_TYPE_SERVER && opts.StickySessionCookieTimeout > 0 {
params["SessionExpireTime"] = fmt.Sprintf("%d", opts.StickySessionCookieTimeout)
}
case api.LB_LISTENER_TYPE_UDP:
if opts.StickySession == api.LB_STICKY_SESSION_TYPE_SERVER && opts.StickySessionCookieTimeout > 0 {
params["SessionExpireTime"] = fmt.Sprintf("%d", opts.StickySessionCookieTimeout)
}
case api.LB_LISTENER_TYPE_HTTP:
case api.LB_LISTENER_TYPE_HTTPS:
if opts.EnableHTTP2 {
params["KeepaliveEnable"] = "1"
}
}
resp, err := self.clbRequest("CreateListener", params)
if err != nil {
return "", errors.Wrapf(err, "CreateListener")
}
ret := []string{}
err = resp.Unmarshal(&ret, "ListenerIds")
if err != nil {
return "", errors.Wrapf(err, "resp.Unmarshal")
}
for i := range ret {
return ret[i], nil
}
return "", errors.Wrapf(cloudprovider.ErrNotFound, resp.String())
}
// https://cloud.tencent.com/document/product/214/30683
// 任务的当前状态。 0:成功,1:失败,2:进行中
func (self *SRegion) GetLBTaskStatus(requestId string) (string, error) {
+119 -23
View File
@@ -45,7 +45,7 @@ const (
PROTOCOL_HTTPS = "HTTPS"
)
type certificate struct {
type Certificate struct {
SSLMode string `json:"SSLMode"`
CERTCAId string `json:"CertCaId"`
CERTId string `json:"CertId"`
@@ -56,7 +56,7 @@ type certificate struct {
1 表示探测后返回值 1xx 表示健康,2 表示返回 2xx 表示健康,4 表示返回 3xx 表示健康,8 表示返回 4xx 表示健康,16 表示返回 5xx 表示健康。
若希望多种码都表示健康,则将相应的值相加。
*/
type healthCheck struct {
type HealthCheck struct {
HTTPCheckDomain string `json:"HttpCheckDomain"`
HealthSwitch int `json:"HealthSwitch"`
HTTPCheckPath string `json:"HttpCheckPath"`
@@ -66,6 +66,7 @@ type healthCheck struct {
HTTPCode int `json:"HttpCode"` // 健康检查状态码(仅适用于HTTP/HTTPS转发规则)。可选值:1~31,默认 31。
HealthNum int `json:"HealthNum"`
TimeOut int `json:"TimeOut"`
CheckType string `json:"CheckType"`
}
type SLBListener struct {
@@ -75,9 +76,9 @@ type SLBListener struct {
lb *SLoadbalancer
Protocol string `json:"Protocol"` // 监听器协议类型,取值 TCP | UDP | HTTP | HTTPS | TCP_SSL
Certificate certificate `json:"Certificate"`
Certificate Certificate `json:"Certificate"`
SniSwitch int64 `json:"SniSwitch"` // 是否开启SNI特性(本参数仅对于HTTPS监听器有意义)
HealthCheck healthCheck `json:"HealthCheck"` // 仅适用于TCP/UDP/TCP_SSL监听器
HealthCheck HealthCheck `json:"HealthCheck"` // 仅适用于TCP/UDP/TCP_SSL监听器
ListenerId string `json:"ListenerId"`
ListenerName string `json:"ListenerName"`
Rules []SLBListenerRule `json:"Rules"` // 监听器下的全部转发规则(本参数仅对于HTTP/HTTPS监听器有意义)
@@ -260,10 +261,7 @@ func (self *SLBListener) GetHealthCheck() string {
}
func (self *SLBListener) GetHealthCheckType() string {
if len(self.HealthCheck.HTTPCheckMethod) > 0 {
return api.LB_HEALTH_CHECK_HTTP
}
return api.LB_HEALTH_CHECK_TCP
return strings.ToLower(self.HealthCheck.CheckType)
}
func (self *SLBListener) GetHealthCheckTimeout() int {
@@ -434,7 +432,7 @@ func (self *SRegion) GetLoadbalancerListeners(lbId string, lblisIds []string, pr
}
// 返回requestId
func (self *SRegion) CreateLoadbalancerListenerRule(lbid string, listenerId string, domain string, url string, scheduler string, sessionExpireTime int, hc *healthCheck) (string, error) {
func (self *SRegion) CreateLoadbalancerListenerRule(lbid string, listenerId string, domain string, url string, scheduler string, sessionExpireTime int, hc *HealthCheck) (string, error) {
if len(lbid) == 0 {
return "", fmt.Errorf("loadbalancer id should not be empty")
}
@@ -498,7 +496,7 @@ func (self *SRegion) DeleteLoadbalancerListener(t LB_TYPE, lbid string, listener
}
// https://cloud.tencent.com/document/product/214/30681
func (self *SRegion) updateLoadbalancerListener(lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *healthCheck, cert *certificate) (string, error) {
func (self *SRegion) updateLoadbalancerListener(lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *HealthCheck, cert *Certificate) (string, error) {
params := map[string]string{
"LoadBalancerId": lbid,
"ListenerId": listenerId,
@@ -526,7 +524,7 @@ func (self *SRegion) updateLoadbalancerListener(lbid string, listenerId string,
return resp.GetString("RequestId")
}
func (self *SRegion) UpdateLoadbalancerListener(t LB_TYPE, lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *healthCheck, cert *certificate) (string, error) {
func (self *SRegion) UpdateLoadbalancerListener(t LB_TYPE, lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *HealthCheck, cert *Certificate) (string, error) {
if len(lbid) == 0 {
return "", fmt.Errorf("loadbalancer id should not be empty")
}
@@ -538,10 +536,10 @@ func (self *SRegion) UpdateLoadbalancerListener(t LB_TYPE, lbid string, listener
return self.updateLoadbalancerListener(lbid, listenerId, listenerName, scheduler, sessionExpireTime, healthCheck, cert)
}
func getHealthCheck(listener *cloudprovider.SLoadbalancerListenerCreateOptions) *healthCheck {
var hc *healthCheck
func getHealthCheck(listener *cloudprovider.SLoadbalancerListenerCreateOptions) *HealthCheck {
var hc *HealthCheck
if listener.HealthCheck == api.LB_BOOL_ON {
hc = &healthCheck{
hc = &HealthCheck{
HealthSwitch: 1,
UnHealthNum: listener.HealthCheckFail,
IntervalTime: listener.HealthCheckInterval,
@@ -557,7 +555,7 @@ func getHealthCheck(listener *cloudprovider.SLoadbalancerListenerCreateOptions)
hc.HTTPCheckPath = listener.HealthCheckURI
}
} else {
hc = &healthCheck{
hc = &HealthCheck{
HealthSwitch: 0,
UnHealthNum: 3,
IntervalTime: 5,
@@ -569,10 +567,10 @@ func getHealthCheck(listener *cloudprovider.SLoadbalancerListenerCreateOptions)
return hc
}
func getListenerRuleHealthCheck(rule *cloudprovider.SLoadbalancerListenerRule) *healthCheck {
var hc *healthCheck
func getListenerRuleHealthCheck(rule *cloudprovider.SLoadbalancerListenerRule) *HealthCheck {
var hc *HealthCheck
if rule.HealthCheck == api.LB_BOOL_ON {
hc = &healthCheck{
hc = &HealthCheck{
HealthSwitch: 1,
UnHealthNum: rule.HealthCheckFail,
IntervalTime: rule.HealthCheckInterval,
@@ -588,7 +586,7 @@ func getListenerRuleHealthCheck(rule *cloudprovider.SLoadbalancerListenerRule) *
hc.HTTPCheckPath = rule.HealthCheckURI
}
} else {
hc = &healthCheck{
hc = &HealthCheck{
HealthSwitch: 0,
UnHealthNum: 3,
IntervalTime: 5,
@@ -600,10 +598,10 @@ func getListenerRuleHealthCheck(rule *cloudprovider.SLoadbalancerListenerRule) *
return hc
}
func getCertificate(listener *cloudprovider.SLoadbalancerListenerCreateOptions) *certificate {
var cert *certificate
func getCertificate(listener *cloudprovider.SLoadbalancerListenerCreateOptions) *Certificate {
var cert *Certificate
if len(listener.CertificateId) > 0 {
cert = &certificate{
cert = &Certificate{
SSLMode: "UNIdIRECTIONAL",
CERTCAId: "",
CERTId: listener.CertificateId,
@@ -661,7 +659,7 @@ func getScheduler(listener *cloudprovider.SLoadbalancerListenerCreateOptions) *s
return &sch
}
func healthCheckParams(t LB_TYPE, params map[string]string, hc *healthCheck, paramPrefix string) map[string]string {
func healthCheckParams(t LB_TYPE, params map[string]string, hc *HealthCheck, paramPrefix string) map[string]string {
if hc == nil {
return params
}
@@ -699,3 +697,101 @@ func (self *SLBListener) GetClientIdleTimeout() int {
func (self *SLBListener) GetBackendConnectTimeout() int {
return 0
}
/*
https://cloud.tencent.com/document/product/214/30693
SNI 特性是什么??
*/
func (self *SRegion) CreateLoadbalancerListener(lbId string, opts *cloudprovider.SLoadbalancerListenerCreateOptions) (string, error) {
params := map[string]string{
"LoadBalancerId": lbId,
"Ports.0": fmt.Sprintf("%d", opts.ListenerPort),
"Protocol": opts.ListenerType,
"ListenerNames.0": opts.Name,
}
switch opts.Scheduler {
case api.LB_SCHEDULER_WRR:
params["Scheduler"] = "WRR"
case api.LB_SCHEDULER_WLC:
params["Scheduler"] = "LEAST_CONN"
case api.LB_SCHEDULER_SCH:
params["Scheduler"] = "IP_HASH"
}
switch opts.ListenerType {
case api.LB_LISTENER_TYPE_TCP:
if opts.StickySession == api.LB_STICKY_SESSION_TYPE_SERVER && opts.StickySessionCookieTimeout > 0 {
params["SessionExpireTime"] = fmt.Sprintf("%d", opts.StickySessionCookieTimeout)
}
if opts.HealthCheck == api.LB_BOOL_ON {
params["HealthCheck.HealthSwitch"] = "1"
params["HealthCheck.TimeOut"] = fmt.Sprintf("%d", opts.HealthCheckTimeout)
params["HealthCheck.IntervalTime"] = fmt.Sprintf("%d", opts.HealthCheckInterval)
params["HealthCheck.HealthNum"] = fmt.Sprintf("%d", opts.HealthCheckRise)
params["HealthCheck.UnHealthNum"] = fmt.Sprintf("%d", opts.HealthCheckFail)
switch opts.HealthCheck {
case api.LB_HEALTH_CHECK_TCP:
params["HealthCheck.CheckType"] = "TCP"
case api.LB_HEALTH_CHECK_HTTP:
params["HealthCheck.HttpVersion"] = "HTTP/1.1"
params["HealthCheck.CheckType"] = "HTTP"
httpCode := 0
for _, code := range strings.Split(opts.HealthCheckHttpCode, ",") {
switch code {
case api.LB_HEALTH_CHECK_HTTP_CODE_1xx:
httpCode += 1
case api.LB_HEALTH_CHECK_HTTP_CODE_2xx:
httpCode += 2
case api.LB_HEALTH_CHECK_HTTP_CODE_3xx:
httpCode += 4
case api.LB_HEALTH_CHECK_HTTP_CODE_4xx:
httpCode += 8
case api.LB_HEALTH_CHECK_HTTP_CODE_5xx:
httpCode += 16
}
}
params["HealthCheck.HttpCheckPath"] = opts.HealthCheckURI
params["HealthCheck.HttpCheckDomain"] = opts.HealthCheckDomain
params["HealthCheck.HttpCode"] = fmt.Sprintf("%d", httpCode)
}
}
case api.LB_LISTENER_TYPE_UDP:
if opts.StickySession == api.LB_STICKY_SESSION_TYPE_SERVER && opts.StickySessionCookieTimeout > 0 {
params["SessionExpireTime"] = fmt.Sprintf("%d", opts.StickySessionCookieTimeout)
}
if opts.HealthCheck == api.LB_BOOL_ON {
params["HealthCheck.HealthSwitch"] = "1"
params["HealthCheck.TimeOut"] = fmt.Sprintf("%d", opts.HealthCheckTimeout)
params["HealthCheck.IntervalTime"] = fmt.Sprintf("%d", opts.HealthCheckInterval)
params["HealthCheck.HealthNum"] = fmt.Sprintf("%d", opts.HealthCheckRise)
params["HealthCheck.UnHealthNum"] = fmt.Sprintf("%d", opts.HealthCheckFail)
switch opts.HealthCheck {
case api.LB_HEALTH_CHECK_PING:
params["HealthCheck.CheckType"] = "PING"
params["HealthCheck.CheckPort"] = "-1"
}
}
case api.LB_LISTENER_TYPE_HTTP:
case api.LB_LISTENER_TYPE_HTTPS:
if opts.EnableHTTP2 {
params["KeepaliveEnable"] = "1"
}
}
resp, err := self.clbRequest("CreateListener", params)
if err != nil {
return "", errors.Wrapf(err, "CreateListener")
}
ret := []string{}
err = resp.Unmarshal(&ret, "ListenerIds")
if err != nil {
return "", errors.Wrapf(err, "resp.Unmarshal")
}
for i := range ret {
return ret[i], nil
}
return "", errors.Wrapf(cloudprovider.ErrNotFound, resp.String())
}
@@ -33,9 +33,9 @@ type SLBListenerRule struct {
listener *SLBListener
Domain string `json:"Domain"`
Certificate certificate `json:"Certificate"`
Certificate Certificate `json:"Certificate"`
URL string `json:"Url"`
HealthCheck healthCheck `json:"HealthCheck"`
HealthCheck HealthCheck `json:"HealthCheck"`
LocationID string `json:"LocationId"`
Scheduler string `json:"Scheduler"`
SessionExpireTime int64 `json:"SessionExpireTime"`