mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 16:13:56 +08:00
1. Add List dispaly Params 'brand' and List filter Params 'brand' for GuestTemplate and ScalingGroup. 2. ScalingActivity's list elem should be sorted in order by StartTime and EndTime 3. HealthCheckCycle and HealthCheckGov in ScalingGroup work now 4. Detach all ScalingGroup when pending delete Guest 5. If the scaling behavior of the scaling group fails n(n is 3 for now) times consecutively, the controller will automatically disablet this group 6. Timer will trigger ScalingPolicy throught request but func call
509 lines
17 KiB
Go
509 lines
17 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package models
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/utils"
|
|
"yunion.io/x/sqlchemy"
|
|
|
|
"yunion.io/x/onecloud/pkg/apis"
|
|
api "yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/util/logclient"
|
|
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
|
)
|
|
|
|
type SScalingPolicyManager struct {
|
|
db.SStatusStandaloneResourceBaseManager
|
|
SScalingGroupResourceBaseManager
|
|
db.SEnabledResourceBaseManager
|
|
}
|
|
|
|
type SScalingPolicy struct {
|
|
db.SStatusStandaloneResourceBase
|
|
SScalingGroupResourceBase
|
|
db.SEnabledResourceBase
|
|
|
|
TriggerType string `width:"16" charset:"ascii" default:"timing" create:"required" list:"user"`
|
|
TriggerId string `width:"128" charset:"ascii"`
|
|
|
|
// Action of scaling activity
|
|
Action string `width:"8" charset:"ascii" default:"set" create:"required" list:"user"`
|
|
Number int `nullable:"false" default:"1" create:"required" list:"user"`
|
|
|
|
// Unit of Number
|
|
Unit string `width:"4" charset:"ascii" create:"required" list:"user"`
|
|
|
|
// Scaling activity triggered by alarms will be rejected during this period about CoolingTime
|
|
CoolingTime int `nullable:"false" default:"300" create:"required" list:"user"`
|
|
}
|
|
|
|
var ScalingPolicyManager *SScalingPolicyManager
|
|
|
|
func init() {
|
|
ScalingPolicyManager = &SScalingPolicyManager{
|
|
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
|
|
SScalingPolicy{},
|
|
"scalingpolicies_tbl",
|
|
"scalingpolicy",
|
|
"scalingpolicies",
|
|
),
|
|
}
|
|
ScalingPolicyManager.SetVirtualObject(ScalingPolicyManager)
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) ValidateListConditions(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
|
var err error
|
|
query, err = spm.SStandaloneResourceBaseManager.ValidateListConditions(ctx, userCred, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !query.Contains("scaling_group") {
|
|
return nil, httperrors.NewInputParameterError("every scaling policy belong to a scaling group")
|
|
}
|
|
return query, nil
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery,
|
|
userCred mcclient.TokenCredential, input api.ScalingPolicyListInput) (*sqlchemy.SQuery, error) {
|
|
var err error
|
|
q, err = spm.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput)
|
|
if err != nil {
|
|
return q, err
|
|
}
|
|
q, err = spm.SScalingGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, input.ScalingGroupFilterListInput)
|
|
if len(input.TriggerType) != 0 {
|
|
q = q.Equals("trigger_type", input.TriggerType)
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
|
q, err := spm.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
if err == nil {
|
|
return q, nil
|
|
}
|
|
return spm.SScalingGroupResourceBaseManager.QueryDistinctExtraField(q, field)
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) FetchParentId(ctx context.Context, data jsonutils.JSONObject) string {
|
|
return spm.SScalingGroupResourceBaseManager.FetchParentId(ctx, data)
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) FilterByParentId(q *sqlchemy.SQuery, parentId string) *sqlchemy.SQuery {
|
|
return spm.SScalingGroupResourceBaseManager.FilterByParentId(q, parentId)
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery,
|
|
userCred mcclient.TokenCredential, query api.ScalingPolicyListInput) (*sqlchemy.SQuery, error) {
|
|
return spm.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput)
|
|
}
|
|
|
|
func (sgm *SScalingPolicy) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, isList bool) (api.ScalingGroupDetails, error) {
|
|
return api.ScalingGroupDetails{}, nil
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) FetchCustomizeColumns(
|
|
ctx context.Context,
|
|
userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject,
|
|
objs []interface{},
|
|
fields stringutils2.SSortedStrings,
|
|
isList bool,
|
|
) []api.ScalingPolicyDetails {
|
|
rows := make([]api.ScalingPolicyDetails, len(objs))
|
|
statusRows := spm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
sgRows := spm.SScalingGroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
|
var err error
|
|
for i := range rows {
|
|
rows[i], err = objs[i].(*SScalingPolicy).getMoreDetails(ctx, userCred, query, isList)
|
|
if err != nil {
|
|
log.Errorf("SScalingPolicy.getMoreDetails error: %s", err)
|
|
}
|
|
rows[i].StatusStandaloneResourceDetails = statusRows[i]
|
|
rows[i].ScalingGroupResourceInfo = sgRows[i]
|
|
}
|
|
return rows
|
|
}
|
|
|
|
func (sp *SScalingPolicy) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, isList bool) (api.ScalingPolicyDetails, error) {
|
|
|
|
var out api.ScalingPolicyDetails
|
|
switch sp.TriggerType {
|
|
case api.TRIGGER_ALARM:
|
|
model, err := ScalingAlarmManager.FetchById(sp.TriggerId)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return out, nil
|
|
}
|
|
if err != nil {
|
|
return out, errors.Wrap(err, "ScalingAlarmManager.FetchById")
|
|
}
|
|
out.Alarm = model.(*SScalingAlarm).AlarmDetails()
|
|
case api.TRIGGER_TIMING:
|
|
model, err := ScalingTimerManager.FetchById(sp.TriggerId)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return out, nil
|
|
}
|
|
if err != nil {
|
|
return out, errors.Wrap(err, "ScalingTimerManager.FetchById")
|
|
}
|
|
out.Timer = model.(*SScalingTimer).TimerDetails()
|
|
case api.TRIGGER_CYCLE:
|
|
model, err := ScalingTimerManager.FetchById(sp.TriggerId)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return out, nil
|
|
}
|
|
if err != nil {
|
|
return out, errors.Wrap(err, "ScalingTimerManager.FetchById")
|
|
}
|
|
out.CycleTimer = model.(*SScalingTimer).CycleTimerDetails()
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential,
|
|
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ScalingPolicyCreateInput) (
|
|
api.ScalingPolicyCreateInput, error) {
|
|
log.Debugf("insert validateCreateData")
|
|
var err error
|
|
input.StandaloneResourceCreateInput, err = spm.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query,
|
|
input.StandaloneResourceCreateInput)
|
|
if err != nil {
|
|
return input, err
|
|
}
|
|
|
|
// check scaling group
|
|
idOrName := input.ScalingGroup
|
|
if len(input.ScalingGroupId) != 0 {
|
|
idOrName = input.ScalingGroupId
|
|
}
|
|
model, err := ScalingGroupManager.FetchByIdOrName(userCred, idOrName)
|
|
if errors.Cause(err) == sql.ErrNoRows {
|
|
return input, httperrors.NewInputParameterError("no such scaling group %s", idOrName)
|
|
}
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "ScalingGroupManager.FetchByIdOrName")
|
|
}
|
|
input.ScalingGroupId = model.GetId()
|
|
|
|
if !utils.IsInStringArray(input.TriggerType, []string{api.TRIGGER_TIMING, api.TRIGGER_CYCLE, api.TRIGGER_ALARM}) {
|
|
return input, httperrors.NewInputParameterError("unkown trigger type %s", input.TriggerType)
|
|
}
|
|
if !utils.IsInStringArray(input.Action, []string{api.ACTION_ADD, api.ACTION_REMOVE, api.ACTION_SET}) {
|
|
return input, httperrors.NewInputParameterError("unkown scaling policy action %s", input.Action)
|
|
}
|
|
if !utils.IsInStringArray(input.Unit, []string{api.UNIT_ONE, api.UNIT_PERCENT}) {
|
|
return input, httperrors.NewInputParameterError("unkown scaling policy unit %s", input.Unit)
|
|
}
|
|
trigger, err := ScalingPolicyManager.Trigger(&input)
|
|
if err != nil {
|
|
return input, errors.Wrap(err, "ScalingPolicyManager.Trigger")
|
|
}
|
|
input, err = trigger.ValidateCreateData(input)
|
|
if err != nil {
|
|
return input, httperrors.NewInputParameterError(err.Error())
|
|
}
|
|
return input, err
|
|
}
|
|
|
|
func (sp *SScalingPolicy) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
// do nothing
|
|
sp.SetStatus(userCred, api.SP_STATUS_DELETING, "")
|
|
return nil
|
|
}
|
|
|
|
func (sp *SScalingPolicy) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
trigger, err := sp.Trigger(nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "SScalingPolicy.Trigger")
|
|
}
|
|
if trigger != nil {
|
|
err = trigger.UnRegister(ctx, userCred)
|
|
if err != nil {
|
|
return errors.Wrap(err, "IScalingTrigger.UnRegister")
|
|
}
|
|
}
|
|
return sp.SResourceBase.Delete(ctx, userCred)
|
|
}
|
|
|
|
func (sp *SScalingPolicy) PostDelete(ctx context.Context, userCred mcclient.TokenCredential) {
|
|
go func() {
|
|
fail := func(sg *SScalingGroup, reason string) {
|
|
if sg != nil {
|
|
logclient.AddActionLogWithContext(ctx, sg, logclient.ACT_DELETE_SCALING_POLICY, reason, userCred, false)
|
|
}
|
|
sp.SetStatus(userCred, api.SP_STATUS_DELETE_FAILED, reason)
|
|
}
|
|
sg, err := sp.ScalingGroup()
|
|
if err != nil {
|
|
fail(nil, err.Error())
|
|
return
|
|
}
|
|
err = sp.RealDelete(ctx, userCred)
|
|
if err != nil {
|
|
fail(sg, err.Error())
|
|
return
|
|
}
|
|
logclient.AddActionLogWithContext(ctx, sg, logclient.ACT_DELETE_SCALING_POLICY, "", userCred, true)
|
|
}()
|
|
}
|
|
|
|
func (spm *SScalingPolicyManager) Trigger(input *api.ScalingPolicyCreateInput) (IScalingTrigger, error) {
|
|
tem := SScalingPolicy{TriggerType: input.TriggerType}
|
|
return tem.Trigger(input)
|
|
}
|
|
|
|
func (sp *SScalingPolicy) Trigger(input *api.ScalingPolicyCreateInput) (IScalingTrigger, error) {
|
|
log.Debugf("inset Trigger")
|
|
if input != nil {
|
|
switch sp.TriggerType {
|
|
case api.TRIGGER_TIMING:
|
|
return &SScalingTimer{
|
|
SScalingPolicyBase: SScalingPolicyBase{sp.GetId()},
|
|
Type: api.TIMER_TYPE_ONCE,
|
|
StartTime: input.Timer.ExecTime,
|
|
EndTime: input.Timer.ExecTime,
|
|
NextTime: input.Timer.ExecTime,
|
|
}, nil
|
|
case api.TRIGGER_CYCLE:
|
|
trigger := &SScalingTimer{
|
|
SScalingPolicyBase: SScalingPolicyBase{sp.GetId()},
|
|
Type: input.CycleTimer.CycleType,
|
|
Minute: input.CycleTimer.Minute,
|
|
Hour: input.CycleTimer.Hour,
|
|
StartTime: input.CycleTimer.StartTime,
|
|
EndTime: input.CycleTimer.EndTime,
|
|
NextTime: time.Time{},
|
|
}
|
|
log.Debugf("setweekdays")
|
|
trigger.SetWeekDays(input.CycleTimer.WeekDays)
|
|
log.Debugf("setmonthdays")
|
|
trigger.SetMonthDays(input.CycleTimer.MonthDays)
|
|
trigger.Update(time.Now())
|
|
return trigger, nil
|
|
case api.TRIGGER_ALARM:
|
|
return &SScalingAlarm{
|
|
SScalingPolicyBase: SScalingPolicyBase{sp.GetId()},
|
|
Cumulate: input.Alarm.Cumulate,
|
|
Cycle: input.Alarm.Cycle,
|
|
Indicator: input.Alarm.Indicator,
|
|
Wrapper: input.Alarm.Wrapper,
|
|
Operator: input.Alarm.Operator,
|
|
Value: input.Alarm.Value,
|
|
RealCumulate: 0,
|
|
LastTriggerTime: time.Now(),
|
|
}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unkown trigger type %s", sp.TriggerType)
|
|
}
|
|
}
|
|
if len(sp.TriggerId) == 0 {
|
|
return nil, nil
|
|
}
|
|
switch sp.TriggerType {
|
|
case api.TRIGGER_TIMING, api.TRIGGER_CYCLE:
|
|
model, err := ScalingTimerManager.FetchById(sp.TriggerId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "SScalingTimerManager.FetchById")
|
|
}
|
|
return model.(*SScalingTimer), nil
|
|
case api.TRIGGER_ALARM:
|
|
model, err := ScalingAlarmManager.FetchById(sp.TriggerId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "SScalingAlarmManager.FetchById")
|
|
}
|
|
return model.(*SScalingAlarm), nil
|
|
default:
|
|
return nil, fmt.Errorf("unkown trigger type %s", sp.TriggerType)
|
|
}
|
|
}
|
|
|
|
func (sp *SScalingPolicy) AllowPerformTrigger(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
|
|
|
sg, err := sp.ScalingGroup()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return sg.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, sp, "trigger")
|
|
}
|
|
|
|
func (sp *SScalingPolicy) PerformTrigger(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
|
|
|
if sp.Status != api.SP_STATUS_READY {
|
|
return nil, httperrors.NewForbiddenError("Can't trigger scaling policy without status 'ready'")
|
|
}
|
|
|
|
var (
|
|
triggerDesc IScalingTriggerDesc
|
|
err error
|
|
)
|
|
if sp.Enabled.IsFalse() {
|
|
return nil, nil
|
|
}
|
|
sg, err := sp.ScalingGroup()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ScalingPolicy.ScalingGroup")
|
|
}
|
|
if sg.Enabled.IsFalse() {
|
|
return nil, nil
|
|
}
|
|
|
|
unmanual, _ := data.Bool("unmanual")
|
|
if !unmanual {
|
|
triggerDesc = SScalingManual{SScalingPolicyBase{sp.Id}}
|
|
} else {
|
|
trigger, err := sp.Trigger(nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "fetch trigger failed")
|
|
}
|
|
if data.Contains("alarm_id") {
|
|
alarmId, _ := data.GetString("alarm_id")
|
|
if alarmId != trigger.(*SScalingAlarm).AlarmId {
|
|
return nil, httperrors.NewInputParameterError("mismatched alarm id")
|
|
}
|
|
}
|
|
if !trigger.IsTrigger() {
|
|
return nil, nil
|
|
}
|
|
triggerDesc = trigger
|
|
}
|
|
err = sg.Scale(ctx, triggerDesc, sp)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ScalingPolicy.Scale")
|
|
}
|
|
if sp.CoolingTime > 0 {
|
|
sg.SetAllowScaleTime(time.Now().Add(time.Duration(sp.CoolingTime) * time.Second))
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func (sp *SScalingPolicy) ScalingGroup() (*SScalingGroup, error) {
|
|
model, err := ScalingGroupManager.FetchById(sp.ScalingGroupId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ScalingGroupManager.FetchById")
|
|
}
|
|
return model.(*SScalingGroup), nil
|
|
}
|
|
|
|
type IScalingAction interface {
|
|
Exec(int) int
|
|
CheckCoolTime() bool
|
|
}
|
|
|
|
func (sp *SScalingPolicy) Exec(from int) int {
|
|
diff := sp.Number
|
|
if sp.Unit == api.UNIT_PERCENT {
|
|
diff = diff * from / 100
|
|
}
|
|
switch sp.Action {
|
|
case api.ACTION_ADD:
|
|
return from + diff
|
|
case api.ACTION_REMOVE:
|
|
return from - diff
|
|
case api.ACTION_SET:
|
|
return diff
|
|
default:
|
|
return from
|
|
}
|
|
}
|
|
|
|
func (sp *SScalingPolicy) CheckCoolTime() bool {
|
|
if sp.TriggerType == api.TRIGGER_ALARM {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (sp *SScalingPolicy) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input apis.PerformEnableInput) bool {
|
|
return true
|
|
}
|
|
|
|
func (sp *SScalingPolicy) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) {
|
|
err := db.EnabledPerformEnable(sp, ctx, userCred, true)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "EnabledPerformEnable")
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (sp *SScalingPolicy) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential,
|
|
query jsonutils.JSONObject, input apis.PerformDisableInput) bool {
|
|
return true
|
|
}
|
|
|
|
func (sp *SScalingPolicy) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
|
|
input apis.PerformDisableInput) (jsonutils.JSONObject, error) {
|
|
err := db.EnabledPerformEnable(sp, ctx, userCred, false)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "EnabledPerformEnable")
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (sg *SScalingPolicy) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
|
sg.SStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
|
|
sg.SetStatus(userCred, api.SP_STATUS_CREATING, "")
|
|
go func() {
|
|
sp, err := sg.ScalingGroup()
|
|
if err != nil {
|
|
log.Errorf("Get ScalingGroup of ScalingPolicy '%s' failed: %s", sg.GetId(), err.Error())
|
|
}
|
|
createFailed := func(reason string) {
|
|
logclient.AddActionLogWithContext(ctx, sp, logclient.ACT_CREATE_SCALING_POLICY, reason, userCred, false)
|
|
sg.SetStatus(userCred, api.SP_STATUS_CREATE_FAILED, reason)
|
|
}
|
|
input := api.ScalingPolicyCreateInput{}
|
|
err = data.Unmarshal(&input)
|
|
if err != nil {
|
|
createFailed(fmt.Sprintf("data.Unmarshal: %s", err.Error()))
|
|
return
|
|
}
|
|
|
|
trigger, err := sg.Trigger(&input)
|
|
if err != nil {
|
|
createFailed(fmt.Sprintf("ScalingPolicy get trigger: %s", err.Error()))
|
|
return
|
|
}
|
|
err = trigger.Register(ctx, userCred)
|
|
if err != nil {
|
|
createFailed(fmt.Sprintf("Trigger.Register: %s", err.Error()))
|
|
return
|
|
}
|
|
logclient.AddActionLogWithContext(ctx, sp, logclient.ACT_CREATE_SCALING_POLICY, "", userCred, true)
|
|
db.Update(sg, func() error {
|
|
sg.TriggerId = trigger.TriggerId()
|
|
sg.Status = api.SP_STATUS_READY
|
|
sg.SetEnabled(true)
|
|
return nil
|
|
})
|
|
}()
|
|
}
|