| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- // 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/cloudcommon/notifyclient"
- "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"
- )
- // +onecloud:swagger-gen-model-singular=scalingpolicy
- // +onecloud:swagger-gen-model-plural=scalingpolicies
- type SScalingPolicyManager struct {
- db.SVirtualResourceBaseManager
- SScalingGroupResourceBaseManager
- db.SEnabledResourceBaseManager
- }
- type SScalingPolicy struct {
- db.SVirtualResourceBase
- 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{
- SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
- 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.SVirtualResourceBaseManager.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.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput)
- if err != nil {
- return q, err
- }
- q, err = spm.SScalingGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, input.ScalingGroupFilterListInput)
- if err != nil {
- return q, err
- }
- q, err = spm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
- if err != nil {
- return q, err
- }
- 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.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
- if err == nil {
- return q, nil
- }
- return spm.SScalingGroupResourceBaseManager.QueryDistinctExtraField(q, field)
- }
- func (sgm *SScalingPolicy) GetUniqValues() jsonutils.JSONObject {
- return jsonutils.Marshal(map[string]string{"scaling_group_id": sgm.ScalingGroupId})
- }
- func (spm *SScalingPolicyManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
- return spm.SScalingGroupResourceBaseManager.FetchUniqValues(ctx, data)
- }
- func (spm *SScalingPolicyManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
- return spm.SScalingGroupResourceBaseManager.FilterByUniqValues(q, values)
- }
- func (spm *SScalingPolicyManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery,
- userCred mcclient.TokenCredential, query api.ScalingPolicyListInput) (*sqlchemy.SQuery, error) {
- return spm.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
- }
- 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.SVirtualResourceBaseManager.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].VirtualResourceDetails = 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.VirtualResourceCreateInput, err = spm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query,
- input.VirtualResourceCreateInput)
- if err != nil {
- return input, err
- }
- // check scaling group
- idOrName := input.ScalingGroup
- if len(input.ScalingGroupId) != 0 {
- idOrName = input.ScalingGroupId
- }
- model, err := ScalingGroupManager.FetchByIdOrName(ctx, 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("%v", err)
- }
- return input, err
- }
- func (sp *SScalingPolicy) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential,
- ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
- // sp.Project must be same with sp.ScalingGroup
- sg, err := sp.ScalingGroup()
- if err != nil {
- return err
- }
- ownerId = sg.GetOwnerId()
- return sp.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
- }
- func (sp *SScalingPolicy) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
- // do nothing
- sp.SetStatus(ctx, 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(ctx, 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()},
- STimer: STimer{
- 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()},
- STimer: STimer{
- 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) 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
- }
- manual, _ := data.Bool("manual")
- if manual {
- 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, sp.CoolingTime)
- if err != nil {
- return nil, errors.Wrap(err, "ScalingPolicy.Scale")
- }
- sp.EventNotify(ctx, userCred)
- return nil, err
- }
- func (sp *SScalingPolicy) EventNotify(ctx context.Context, userCred mcclient.TokenCredential) {
- notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
- Obj: sp,
- Action: notifyclient.ActionExecute,
- })
- }
- 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) 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) 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(ctx, 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(ctx, 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
- })
- }()
- }
|