Merge pull request #6691 from rainzm/scheduledtasks/init

Scheduledtasks/init
This commit is contained in:
yunion-ci-robot
2020-06-09 17:06:07 +08:00
committed by GitHub
23 changed files with 1776 additions and 222 deletions

View File

@@ -15,6 +15,7 @@
package compute
import (
"fmt"
"time"
"yunion.io/x/jsonutils"
@@ -25,6 +26,20 @@ import (
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type Timer struct {
TimingExecTime string `help:"Exectime for 'timing' type trigger, format:'2006-01-02 15:04:05'" json:"exec_time"`
}
type CycleTimer struct {
CycleCycleType string `help:"Cycle type for cycle timer" json:"cycle_type" choices:"day|week|month"`
CycleMinute int `help:"Minute of cycle timer" json:"minute"`
CycleHour int `help:"Hour of cycle timer" json:"hour"`
CycleWeekdays []int `help:"Weekdays for cycle timer" json:"weekdays"`
CycleMonthDays []int `help:"Month days for cycle timer" json:"month_days"`
CycleStartTime string `help:"Start time for cycle timer, format:'2006-01-02 15:04:05'" json:"start_time"`
CycleEndTime string `help:"End time for cycle timer, format:'2006-01-02 15:04:05'" json:"end_time"`
}
func init() {
type ScalingPolicyListOptions struct {
options.BaseListOptions
@@ -57,20 +72,6 @@ func init() {
return nil
})
type ScalingTimer struct {
TimingExecTime time.Time `help:"Exectime for 'timing' type trigger" json:"exec_time"`
}
type ScalingCycleTimer struct {
CycleCycleType string `help:"Cycle type for 'cycle' type trigger" json:"cycle_type"`
CycleMinute int `help:"Minute of 'cycle' type trigger" json:"minute"`
CycleHour int `help:"Hour of 'cycle' type trigger" json:"hour"`
CycleWeekdays []int `help:"Weekdays for 'cycle' type trigger" json:"weekdays"`
CycleMonthDays []int `help:"Month days for 'cycle' type trigger" json:"month_days"`
CycleStartTime time.Time `help:"Start time for 'cycle' type trigger" json:"start_time"`
CycleEndTime time.Time `help:"End time for 'cycle' type trigger" json:"end_time"`
}
type ScalingAlarm struct {
AlarmCumulate int `help:"Cumulate times alarm will trigger, for 'alarm' trigger" json:"cumulate"`
AlarmCycle int `help:"Monitoring cycle for indicators, for 'alarm' trigger" json:"cycle"`
@@ -85,8 +86,8 @@ func init() {
ScalingGroup string `help:"ScalingGroup ID or Name" json:"scaling_group"`
TriggerType string `help:"Trigger type" choices:"alarm|timing|cycle" json:"trigger_type"`
ScalingTimer
ScalingCycleTimer
Timer
CycleTimer
ScalingAlarm
Action string `help:"Action for scaling policy" choices:"add|remove|set" json:"action"`
@@ -97,20 +98,33 @@ func init() {
R(&ScalingPolicyCreateOptions{}, "scaling-policy-create", "Create Scaling Policy",
func(s *mcclient.ClientSession, args *ScalingPolicyCreateOptions) error {
formatStr := "2006-01-02 15:04:05"
timingExecTime, err := time.Parse(formatStr, args.TimingExecTime)
if err != nil {
return fmt.Errorf("invalid time format for 'exec_time'")
}
cycleStarTime, err := time.Parse(formatStr, args.CycleStartTime)
if err != nil {
return fmt.Errorf("invalid time format for 'start_time'")
}
cycleEndTime, err := time.Parse(formatStr, args.CycleEndTime)
if err != nil {
return fmt.Errorf("invalid time format for 'end_time'")
}
spCreateInput := api.ScalingPolicyCreateInput{
ScalingGroup: args.ScalingGroup,
TriggerType: args.TriggerType,
Timer: api.ScalingTimerCreateInput{
ExecTime: args.TimingExecTime,
Timer: api.TimerCreateInput{
ExecTime: timingExecTime,
},
CycleTimer: api.ScalingCycleTimerCreateInput{
CycleTimer: api.CycleTimerCreateInput{
CycleType: args.CycleCycleType,
Minute: args.CycleMinute,
Hour: args.CycleHour,
WeekDays: args.CycleWeekdays,
MonthDays: args.CycleMonthDays,
StartTime: args.CycleStartTime,
EndTime: args.CycleEndTime,
StartTime: cycleStarTime,
EndTime: cycleEndTime,
},
Alarm: api.ScalingAlarmCreateInput{
Cumulate: args.AlarmCumulate,

View File

@@ -0,0 +1,211 @@
// 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 compute
import (
"fmt"
"time"
"yunion.io/x/jsonutils"
apis "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func init() {
type ScheduledTaskListOptions struct {
options.BaseListOptions
ScheduledType string `help:"scheduled type" choices:"timing|cycle"`
ResourceType string `help:"resource type"`
Operation string `help:"operation"`
}
R(&ScheduledTaskListOptions{}, "scheduledtask-list", "list Scheduled Task", func(s *mcclient.ClientSession, args *ScheduledTaskListOptions) error {
params, err := options.ListStructToParams(args)
tasks, err := modules.ScheduledTask.List(s, params)
if err != nil {
return err
}
printList(tasks, modules.ScheduledTask.GetColumns(s))
return nil
})
type ScheduledTaskShowOptions struct {
ID string `help:"ScheduledTask ID or Name"`
}
R(&ScheduledTaskShowOptions{}, "scheudled-task-show", "Show Scheduled Task", func(s *mcclient.ClientSession,
args *ScheduledTaskShowOptions) error {
params := jsonutils.NewDict()
params.Set("details", jsonutils.JSONTrue)
task, err := modules.ScheduledTask.Get(s, args.ID, params)
if err != nil {
return err
}
printObject(task)
return nil
})
type ScheduledTaskCreateOptions struct {
NAME string `help:"ScheduledTask Name" json:"name"`
ScheduledType string `help:"Scheudled Type" choices:"timing|cycle" json:"scheduled_type"`
Timer
CycleTimer
ResourceType string `help:"resource type"`
Operation string `help:"operation"`
LabelType string `help:"label type"`
Labels []string `help:"labels"`
}
R(&ScheduledTaskCreateOptions{}, "scheduledtask-create", "Create Scheduled Task", func(s *mcclient.ClientSession, args *ScheduledTaskCreateOptions) error {
formatStr := "2006-01-02 15:04:05"
var exectime, starttime, endtime time.Time
var err error
if len(args.TimingExecTime) > 0 {
exectime, err = time.Parse(formatStr, args.TimingExecTime)
if err != nil {
return fmt.Errorf("invalid time format for 'exec_time'")
}
}
if len(args.CycleStartTime) > 0 {
starttime, err = time.Parse(formatStr, args.CycleStartTime)
if err != nil {
return fmt.Errorf("invalid time format for 'start_time'")
}
}
if len(args.CycleEndTime) > 0 {
endtime, err = time.Parse(formatStr, args.CycleEndTime)
if err != nil {
return fmt.Errorf("invalid time format for 'end_time'")
}
}
stCreateInput := apis.ScheduledTaskCreateInput{
ScheduledType: args.ScheduledType,
Timer: apis.TimerCreateInput{
ExecTime: exectime,
},
CycleTimer: apis.CycleTimerCreateInput{
CycleType: args.CycleCycleType,
Minute: args.CycleMinute,
Hour: args.CycleHour,
WeekDays: args.CycleWeekdays,
MonthDays: args.CycleMonthDays,
StartTime: starttime,
EndTime: endtime,
},
ResourceType: args.ResourceType,
Operation: args.Operation,
LabelType: args.LabelType,
Labels: args.Labels,
}
stCreateInput.Name = args.NAME
ret, err := modules.ScheduledTask.Create(s, jsonutils.Marshal(stCreateInput))
if err != nil {
return err
}
printObject(ret)
return nil
})
type ScheduledTaskEnableOptions struct {
ID string `help:"ScheduledTask ID or Name"`
}
R(&ScheduledTaskEnableOptions{}, "scheduledtask-enable", "Enable ScheduledTask", func(s *mcclient.ClientSession,
args *ScheduledTaskEnableOptions) error {
ret, err := modules.ScheduledTask.PerformAction(s, args.ID, "enable", jsonutils.NewDict())
if err != nil {
return err
}
printObject(ret)
return nil
})
R(&ScheduledTaskEnableOptions{}, "scheduledtask-disable", "Disable ScheduledTask",
func(s *mcclient.ClientSession, args *ScheduledTaskEnableOptions) error {
ret, err := modules.ScheduledTask.PerformAction(s, args.ID, "disable", jsonutils.NewDict())
if err != nil {
return err
}
printObject(ret)
return nil
},
)
type ScheduledTaskSetLabelsOptions struct {
ID string `help:"ScheduledTask ID or Name"`
Labels []string `help:"Label"`
}
R(&ScheduledTaskSetLabelsOptions{}, "scheduledtask-setlabels", "Trigger ScheduledTask's action",
func(s *mcclient.ClientSession, args *ScheduledTaskSetLabelsOptions) error {
params := jsonutils.NewDict()
params.Set("labels", jsonutils.Marshal(args.Labels))
ret, err := modules.ScheduledTask.PerformAction(s, args.ID, "set-labels", params)
if err != nil {
return err
}
printObject(ret)
return nil
},
)
type ScheduledTaskTriggerOptions struct {
ID string `help:"ScheduledTask ID or Name"`
}
R(&ScheduledTaskTriggerOptions{}, "scheduledtask-trigger", "Trigger ScheduledTask",
func(s *mcclient.ClientSession, args *ScheduledTaskTriggerOptions) error {
params := jsonutils.NewDict()
ret, err := modules.ScheduledTask.PerformAction(s, args.ID, "trigger", params)
if err != nil {
return err
}
printObject(ret)
return nil
},
)
type ScheduledTaskDeleteOptions struct {
ID string `help:"ScheduledTask ID or Name"`
}
R(&ScheduledTaskDeleteOptions{}, "scheduledtask-delete", "Delete ScheduledTask",
func(s *mcclient.ClientSession, args *ScheduledTaskDeleteOptions) error {
ret, err := modules.ScheduledTask.Delete(s, args.ID, jsonutils.NewDict())
if err != nil {
return err
}
printObject(ret)
return nil
},
)
type ScheduledTaskAvtivityListOptions struct {
options.BaseListOptions
ScheduledTask string `help:"Scheduled Task" json:"scheduled_task"`
}
R(&ScheduledTaskAvtivityListOptions{}, "scheduledtask-activity-list", "List Scheduled Task Activity",
func(s *mcclient.ClientSession, args *ScheduledTaskAvtivityListOptions) error {
params, err := options.ListStructToParams(args)
if err != nil {
return err
}
list, err := modules.ScheduledTaskActivity.List(s, params)
if err != nil {
return err
}
printList(list, modules.ScheduledTaskActivity.GetColumns(s))
return nil
},
)
}

View File

@@ -150,7 +150,7 @@ type ScalingGroupResourceInfo struct {
}
type ScalingGroupFilterListInput struct {
// descirption: 伸缩组 Id or Name
// description: 伸缩组 Id or Name
// example: sg-1234
ScalingGroup string `json:"scaling_group"`
}

View File

@@ -21,9 +21,9 @@ type ScalingPolicyDetails struct {
ScalingGroupResourceInfo
SScalingPolicy
// 定时方式触发
Timer ScalingTimerDetails `json:"timer"`
Timer TimerDetails `json:"timer"`
// 周期方式触发
CycleTimer ScalingCycleTimerDetails `json:"cycle_timer"`
CycleTimer CycleTimerDetails `json:"cycle_timer"`
// 告警方式触发
Alarm ScalingAlarmDetails `json:"alarm"`
}
@@ -43,9 +43,9 @@ type ScalingPolicyCreateInput struct {
// enum: timing,cycle,alarm
TriggerType string `json:"trigger_type"`
Timer ScalingTimerCreateInput `json:"timer"`
CycleTimer ScalingCycleTimerCreateInput `json:"cycle_timer"`
Alarm ScalingAlarmCreateInput `json:"alarm"`
Timer TimerCreateInput `json:"timer"`
CycleTimer CycleTimerCreateInput `json:"cycle_timer"`
Alarm ScalingAlarmCreateInput `json:"alarm"`
// desciption: 伸缩策略的行为(增加还是删除或者调整为)
// enum: add,remove,set

View File

@@ -16,13 +16,13 @@ package compute
import "time"
type ScalingTimerCreateInput struct {
type TimerCreateInput struct {
// description: 执行时间
ExecTime time.Time
}
type ScalingCycleTimerCreateInput struct {
type CycleTimerCreateInput struct {
// description: 周期类型
// enum: day,week,month
@@ -81,12 +81,12 @@ type ScalingAlarmCreateInput struct {
Value float64 `json:"value"`
}
type ScalingTimerDetails struct {
type TimerDetails struct {
// description: 执行时间
ExecTime time.Time `json:"exec_time"`
}
type ScalingCycleTimerDetails struct {
type CycleTimerDetails struct {
// description: 周期类型:按天/周/月
CycleType string `json:"cycle_type"`
// description: 分钟

View File

@@ -0,0 +1,120 @@
// 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 compute
import (
"time"
"yunion.io/x/onecloud/pkg/apis"
)
type ScheduledTaskDetails struct {
apis.VirtualResourceDetails
SScheduledTask
// 定时方式触发
Timer TimerDetails `json:"timer"`
// 周期方式触发
CycleTimer CycleTimerDetails `json:"cycle_timer"`
// 绑定的所有标示
Labels []string `json:"labels,allowempty"`
LabelDetails []LabelDetail `json:"label_details,allowempty"`
}
type LabelDetail struct {
Label string `json:"label"`
IsolatedTime time.Time `json:"isolated_time"`
}
type ScheduledTaskListInput struct {
apis.VirtualResourceListInput
apis.EnabledResourceBaseListInput
// description: resource type
// example: server
// enum: server
ResourceType string `json:"resource_type"`
// description: label type
// example: tag
LabelType string `json:"label_type"`
// description: operation
// example: stop
// enum: start,stop,restart
Operation string `json:"operation"`
}
type ScheduledTaskCreateInput struct {
apis.VirtualResourceCreateInput
apis.EnabledBaseResourceCreateInput
// description: scheduled type
// enum: cycle,timing
// example: timing
ScheduledType string `json:"scheduled_type"`
Timer TimerCreateInput `json:"timer"`
CycleTimer CycleTimerCreateInput `json:"cycle_timer"`
// description: resource type
// enum: server
// example: server
ResourceType string `json:"resource_type"`
// description: operation
// enum: start,stop,restart
// example: stop
Operation string `json:"operation"`
// description: label type
// enum: tag,id
// example: id
LabelType string `json:"label_type"`
// description: labels
// example: {g-12345}
Labels []string
}
type ScheduledTaskResourceInfo struct {
// description: 定时任务名称
// example: st-nihao
ScheduledTask string `json:"scheduled_task"`
// description: 定时任务ID
// example: 1234
ScheduledTaskId string `json:"scheduled_task_id"`
}
type ScheduledTaskFilterListInput struct {
// description: 定时任务 Id or Name
// example: st-1234
ScheduledTask string `json:"scheduled_task"`
}
type ScheduledTaskActivityDetails struct {
apis.StatusStandaloneResourceDetails
SScheduledTaskActivity
}
type ScheduledTaskActivityListInput struct {
apis.StatusStandaloneResourceListInput
// description: 定时任务 ID or Name
// example: st-11212
ScheduledTask string `json:"scheduled_task"`
}
type ScheduledTaskSetLabelsInput struct {
Labels []string `json:"labels"`
}
type ScheduledTaskTriggerInput struct {
}

View File

@@ -0,0 +1,38 @@
// 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 compute
const (
ST_TYPE_TIMING = "timing" // 定时
ST_TYPE_CYCLE = "cycle" // 周期
ST_STATUS_READY = "ready"
ST_STATUS_CREATE_FAILED = "create_failed"
ST_RESOURCE_SERVER = "server"
ST_RESOURCE_OPERATION_START = "start"
ST_RESOURCE_OPERATION_STOP = "stop"
ST_RESOURCE_OPERATION_RESTART = "restart"
ST_LABEL_ID = "id"
ST_LABEL_TAG = "tag"
ST_ACTIVITY_STATUS_EXEC = "execution" // 执行中
ST_ACTIVITY_STATUS_SUCCEED = "succeed" // 成功
ST_ACTIVITY_STATUS_PART_SUCCEED = "part_succeed" // 部分成功
ST_ACTIVITY_STATUS_FAILED = "failed" // 失败
ST_ACTIVITY_STATUS_REJECT = "reject" // 拒绝
)

View File

@@ -855,7 +855,8 @@ type SGuestTemplate struct {
// 计费方式
BillingType string `json:"billing_type"`
// 其他配置信息
Content interface{} `json:"content"`
Content interface{} `json:"content"`
LastCheckTime time.Time `json:"last_check_time"`
}
// SGuestTemplateResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestTemplateResourceBase.
@@ -1812,17 +1813,7 @@ type SScalingPolicyBase struct {
type SScalingTimer struct {
apis.SStandaloneResourceBase
SScalingPolicyBase
// Timer type
Type string `json:"type"`
// 0-59
Minute int `json:"minute"`
// 0-23
Hour int `json:"hour"`
// 0-7 1 is Monday 0 is unlimited
WeekDays byte `json:"week_days"`
// 0-31 0 is unlimited
MonthDays uint32 `json:"month_days"`
IsExpired bool `json:"is_expired"`
STimer
}
// SSchedpolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSchedpolicy.
@@ -1855,6 +1846,33 @@ type SSchedtagResourceBase struct {
SchedtagId string `json:"schedtag_id"`
}
// SScheduledTask is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SScheduledTask.
type SScheduledTask struct {
apis.SVirtualResourceBase
apis.SEnabledResourceBase
ScheduledType string `json:"scheduled_type"`
STimer
ResourceType string `json:"resource_type"`
Operation string `json:"operation"`
LabelType string `json:"label_type"`
}
// SScheduledTaskActivity is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SScheduledTaskActivity.
type SScheduledTaskActivity struct {
apis.SStatusStandaloneResourceBase
ScheduledTaskId string `json:"scheduled_task_id"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Reason string `json:"reason"`
}
// SScheduledTaskLabel is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SScheduledTaskLabel.
type SScheduledTaskLabel struct {
apis.SVirtualJointResourceBase
ScheduledTaskId string `json:"scheduled_task_id"`
Label string `json:"label"`
}
// SSecurityGroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroup.
type SSecurityGroup struct {
apis.SSharableVirtualResourceBase
@@ -2071,6 +2089,27 @@ type SSyncableBaseResource struct {
LastSyncEndAt time.Time `json:"last_sync_end_at"`
}
// STimer is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.STimer.
type STimer struct {
// Cycle type
Type string `json:"type"`
// 0-59
Minute int `json:"minute"`
// 0-23
Hour int `json:"hour"`
// 0-7 1 is Monday 0 is unlimited
WeekDays byte `json:"week_days"`
// 0-31 0 is unlimited
MonthDays uint32 `json:"month_days"`
// StartTime represent the start time of this timer
StartTime time.Time `json:"start_time"`
// EndTime represent deadline of this timer
EndTime time.Time `json:"end_time"`
// NextTime represent the time timer should bell
NextTime time.Time `json:"next_time"`
IsExpired bool `json:"is_expired"`
}
// SVCenter is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SVCenter.
type SVCenter struct {
apis.SEnabledStatusStandaloneResourceBase

View File

@@ -4675,6 +4675,22 @@ func (self *SGuest) PendingDetachScalingGroup() error {
return nil
}
func (self *SGuest) DetachScheduledTask(ctx context.Context, userCred mcclient.TokenCredential) error {
q := ScheduledTaskLabelManager.Query().Equals("label", self.Id)
stls := make([]SScheduledTaskLabel, 0, 1)
err := db.FetchModelObjects(ScheduledTaskLabelManager, q, &stls)
if err != nil {
return err
}
for i := range stls {
err := stls[i].Detach(ctx, userCred)
if err != nil {
return err
}
}
return nil
}
func (self *SGuest) DeleteEip(ctx context.Context, userCred mcclient.TokenCredential) error {
eip, err := self.GetEip()
if err != nil {

View File

@@ -56,6 +56,8 @@ func InitDB() error {
DynamicschedtagManager,
ServerSkuManager,
ElasticcacheSkuManager,
ScheduledTaskActivityManager,
} {
err := manager.InitializeData()
if err != nil {

View File

@@ -304,20 +304,24 @@ func (sp *SScalingPolicy) Trigger(input *api.ScalingPolicyCreateInput) (IScaling
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,
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()},
Type: input.CycleTimer.CycleType,
Minute: input.CycleTimer.Minute,
Hour: input.CycleTimer.Hour,
StartTime: input.CycleTimer.StartTime,
EndTime: input.CycleTimer.EndTime,
NextTime: time.Time{},
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)

View File

@@ -18,7 +18,6 @@ import (
"context"
"database/sql"
"fmt"
"sort"
"strconv"
"strings"
"time"
@@ -35,7 +34,6 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
"yunion.io/x/onecloud/pkg/util/bitmap"
)
type IScalingTriggerDesc interface {
@@ -97,30 +95,7 @@ type SScalingTimer struct {
SScalingPolicyBase
// Timer type
Type string `width:"8" charset:"ascii"`
// 0-59
Minute int `nullable:"false"`
// 0-23
Hour int `nullable:"false"`
// 0-7 1 is Monday 0 is unlimited
WeekDays uint8 `nullable:"false"`
// 0-31 0 is unlimited
MonthDays uint32 `nullable:"false"`
// StartTime represent the start time of this timer
StartTime time.Time
// EndTime represent deadline of this timer
EndTime time.Time
// NextTime represent the time timer should bell
NextTime time.Time `index:"true"`
IsExpired bool
STimer
}
type SScalingAlarmManager struct {
@@ -181,111 +156,6 @@ func init() {
ScalingAlarmManager.SetVirtualObject(ScalingAlarmManager)
}
func (st *SScalingTimer) GetWeekDays() []int {
return bitmap.Uint2IntArray(uint32(st.WeekDays))
}
func (st *SScalingTimer) GetMonthDays() []int {
return bitmap.Uint2IntArray(st.MonthDays)
}
func (st *SScalingTimer) SetWeekDays(days []int) {
st.WeekDays = uint8(bitmap.IntArray2Uint(days))
}
func (st *SScalingTimer) SetMonthDays(days []int) {
st.MonthDays = bitmap.IntArray2Uint(days)
}
// Update will update the SScalingTimer
func (st *SScalingTimer) Update(now time.Time) {
if now.IsZero() {
now = time.Now()
}
if !now.Before(st.EndTime) {
st.IsExpired = true
return
}
if now.Before(st.StartTime) {
now = st.StartTime
}
if !st.NextTime.Before(now) {
return
}
newNextTime := time.Date(now.Year(), now.Month(), now.Day(), st.Hour, st.Minute, 0, 0, now.Location())
if now.After(newNextTime) {
newNextTime = newNextTime.AddDate(0, 0, 1)
}
switch {
case st.WeekDays != 0:
// week
nowDay, weekdays := int(newNextTime.Weekday()), st.GetWeekDays()
if nowDay == 0 {
nowDay = 7
}
// weekdays[0]+7 is for the case that all time nodes has been missed in this week
weekdays = append(weekdays, weekdays[0]+7)
index := sort.SearchInts(weekdays, nowDay)
newNextTime = newNextTime.AddDate(0, 0, weekdays[index]-nowDay)
case st.MonthDays != 0:
// month
monthdays := st.GetMonthDays()
suitTime := newNextTime
for {
day := suitTime.Day()
index := sort.SearchInts(monthdays, day)
if index == len(monthdays) || monthdays[index] > st.MonthDaySum(suitTime) {
// set suitTime as the first day of next month
suitTime = suitTime.AddDate(0, 1, -suitTime.Day()+1)
continue
}
newNextTime = time.Date(suitTime.Year(), suitTime.Month(), monthdays[index], suitTime.Hour(),
suitTime.Minute(), 0, 0, suitTime.Location())
break
}
default:
// day
}
log.Debugf("The final NextTime: %s", newNextTime)
st.NextTime = newNextTime
if st.NextTime.After(st.EndTime) {
st.IsExpired = true
}
}
// MonthDaySum calculate the number of month's days
func (st *SScalingTimer) MonthDaySum(t time.Time) int {
year, month := t.Year(), t.Month()
monthDays := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
if month != 2 {
return monthDays[2]
}
if year%4 != 0 || (year%100 == 0 && year%400 != 0) {
return 28
}
return 29
}
func (st *SScalingTimer) TimerDetails() api.ScalingTimerDetails {
return api.ScalingTimerDetails{ExecTime: st.EndTime}
}
func (st *SScalingTimer) CycleTimerDetails() api.ScalingCycleTimerDetails {
out := api.ScalingCycleTimerDetails{
Minute: st.Minute,
Hour: st.Hour,
WeekDays: st.GetWeekDays(),
MonthDays: st.GetMonthDays(),
StartTime: st.StartTime,
EndTime: st.EndTime,
CycleType: st.Type,
}
return out
}
func (sa *SScalingAlarm) AlarmDetails() api.ScalingAlarmDetails {
return api.ScalingAlarmDetails{
Cumulate: sa.Cumulate,
@@ -298,40 +168,13 @@ func (sa *SScalingAlarm) AlarmDetails() api.ScalingAlarmDetails {
}
func (st *SScalingTimer) ValidateCreateData(input api.ScalingPolicyCreateInput) (api.ScalingPolicyCreateInput, error) {
now := time.Now()
var err error
if input.TriggerType == api.TRIGGER_TIMING {
if now.After(input.Timer.ExecTime) {
return input, fmt.Errorf("exec_time is earlier than now")
}
return input, nil
input.Timer, err = checkTimerCreateInput(input.Timer)
return input, err
}
if input.CycleTimer.Minute < 0 || input.CycleTimer.Minute > 59 {
return input, fmt.Errorf("minute should between 0 and 59")
}
if input.CycleTimer.Hour < 0 || input.CycleTimer.Hour > 23 {
return input, fmt.Errorf("hour should between 0 and 23")
}
switch input.CycleTimer.CycleType {
case api.TIMER_TYPE_DAY:
input.CycleTimer.WeekDays = []int{}
input.CycleTimer.MonthDays = []int{}
case api.TIMER_TYPE_WEEK:
if len(input.CycleTimer.WeekDays) == 0 {
return input, fmt.Errorf("week_days should not be empty")
}
input.CycleTimer.MonthDays = []int{}
case api.TIMER_TYPE_MONTH:
if len(input.CycleTimer.MonthDays) == 0 {
return input, fmt.Errorf("month_days should not be empty")
}
input.CycleTimer.WeekDays = []int{}
default:
return input, fmt.Errorf("unkown cycle type %s", input.CycleTimer.CycleType)
}
if now.After(input.CycleTimer.EndTime) {
return input, fmt.Errorf("end_time is earlier than now")
}
return input, nil
input.CycleTimer, err = checkCycleTimerCreateInput(input.CycleTimer)
return input, err
}
func (st *SScalingTimer) Register(ctx context.Context, userCred mcclient.TokenCredential) error {

View File

@@ -22,14 +22,14 @@ import (
"yunion.io/x/onecloud/pkg/util/bitmap"
)
func TestSScalingTimer_Update(t *testing.T) {
func TestSTimer_Update(t *testing.T) {
loc, _ := time.LoadLocation("Asia/Shanghai")
ins := []struct {
Timer *SScalingTimer
Timer *STimer
UpdateTimes int
}{
{
Timer: &SScalingTimer{
Timer: &STimer{
Hour: 7,
Minute: 21,
Type: compute.TIMER_TYPE_DAY,
@@ -40,7 +40,7 @@ func TestSScalingTimer_Update(t *testing.T) {
UpdateTimes: 2,
},
{
Timer: &SScalingTimer{
Timer: &STimer{
Hour: 7,
Minute: 21,
WeekDays: uint8(bitmap.IntArray2Uint([]int{1, 5, 7})),
@@ -52,7 +52,7 @@ func TestSScalingTimer_Update(t *testing.T) {
UpdateTimes: 4,
},
{
Timer: &SScalingTimer{
Timer: &STimer{
Hour: 7,
Minute: 21,
MonthDays: bitmap.IntArray2Uint([]int{1, 10, 30}),
@@ -82,7 +82,7 @@ func TestSScalingTimer_Update(t *testing.T) {
{},
},
}
wrapper := func(timer *SScalingTimer) time.Time {
wrapper := func(timer *STimer) time.Time {
fakeNow := timer.NextTime
if !fakeNow.IsZero() {
fakeNow = fakeNow.Add(time.Minute)

View File

@@ -0,0 +1,798 @@
// 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"
"fmt"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/sets"
"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"
cop "yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
var ScheduledTaskManager *SScheduledTaskManager
func init() {
ScheduledTaskManager = &SScheduledTaskManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SScheduledTask{},
"scheduledtasks_tbl",
"scheduledtask",
"scheduledtasks",
),
}
ScheduledTaskManager.SetVirtualObject(ScheduledTaskManager)
}
// +onecloud:swagger-gen-model-singular=scheduledtask
// +onecloud:swagger-gen-model-singular=scheduledtasks
type SScheduledTaskManager struct {
db.SVirtualResourceBaseManager
db.SEnabledResourceBaseManager
}
type SScheduledTask struct {
db.SVirtualResourceBase
db.SEnabledResourceBase
ScheduledType string `width:"16" charset:"ascii" create:"required" list:"user" get:"user"`
STimer
TimerDesc string `width:"128" charset:"utf8" list:"user" get:"user"`
ResourceType string `width:"32" charset:"ascii" create:"required" list:"user" get:"user"`
Operation string `width:"32" charset:"ascii" create:"required" list:"user" get:"user"`
LabelType string `width:"4" charset:"ascii" create:"required" list:"user" get:"user"`
}
func (stm *SScheduledTaskManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ScheduledTaskListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = stm.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput)
if err != nil {
return q, err
}
q, err = stm.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
if err != nil {
return q, err
}
if len(input.Operation) > 0 {
q = q.Equals("operation", input.Operation)
}
if len(input.ResourceType) > 0 {
q = q.Equals("resource_type", input.ResourceType)
}
if len(input.LabelType) > 0 {
q = q.Equals("label_type", input.LabelType)
}
return q, nil
}
func (stm *SScheduledTaskManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery,
userCred mcclient.TokenCredential, query api.ScalingPolicyListInput) (*sqlchemy.SQuery, error) {
return stm.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
}
func (stm *SScheduledTask) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, isList bool) (api.ScheduledTaskDetails, error) {
return api.ScheduledTaskDetails{}, nil
}
func (stm *SScheduledTaskManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.ScheduledTaskDetails {
rows := make([]api.ScheduledTaskDetails, len(objs))
virRows := stm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
var err error
for i := range rows {
rows[i], err = objs[i].(*SScheduledTask).getMoreDetails(ctx, userCred, query, isList)
if err != nil {
log.Errorf("SScheduledTask.getMoreDetails error: %s", err)
}
rows[i].VirtualResourceDetails = virRows[i]
}
return rows
}
func (st *SScheduledTask) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.ScheduledTaskDetails, error) {
var out api.ScheduledTaskDetails
switch st.ScheduledType {
case api.ST_TYPE_TIMING:
out.Timer = st.STimer.TimerDetails()
case api.ST_TYPE_CYCLE:
out.CycleTimer = st.STimer.CycleTimerDetails()
}
// fill label
stLabels, err := st.STLabels()
if err != nil {
return out, err
}
out.Labels = make([]string, len(stLabels))
out.LabelDetails = make([]api.LabelDetail, len(stLabels))
for i := range stLabels {
out.Labels[i] = stLabels[i].Label
out.LabelDetails[i].IsolatedTime = stLabels[i].CreatedAt
out.LabelDetails[i].Label = stLabels[i].Label
}
return out, nil
}
func (stm *SScheduledTaskManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ScheduledTaskCreateInput) (api.ScheduledTaskCreateInput, error) {
var err error
input.VirtualResourceCreateInput, err = stm.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput)
if err != nil {
return input, err
}
if !utils.IsInStringArray(input.ScheduledType, []string{api.ST_TYPE_TIMING, api.ST_TYPE_CYCLE}) {
return input, httperrors.NewInputParameterError("unkown scheduled type '%s'", input.ScheduledType)
}
if !utils.IsInStringArray(input.ResourceType, []string{api.ST_RESOURCE_SERVER}) {
return input, httperrors.NewInputParameterError("unkown resource type '%s'", input.ResourceType)
}
if !utils.IsInStringArray(input.Operation, []string{api.ST_RESOURCE_OPERATION_RESTART, api.ST_RESOURCE_OPERATION_STOP, api.ST_RESOURCE_OPERATION_START}) {
return input, httperrors.NewInputParameterError("unkown resource operation '%s'", input.Operation)
}
if !utils.IsInStringArray(input.LabelType, []string{api.ST_LABEL_ID, api.ST_LABEL_TAG}) {
return input, httperrors.NewInputParameterError("unkown label type '%s'", input.LabelType)
}
// check timer or cycletimer
if input.ScheduledType == api.ST_TYPE_TIMING {
input.Timer, err = checkTimerCreateInput(input.Timer)
} else {
input.CycleTimer, err = checkCycleTimerCreateInput(input.CycleTimer)
}
return input, err
}
var wdsCN = []string{"", "一", "二", "三", "四", "五", "六", "日"}
var zone = time.FixedZone("GMT", 8*3600)
func (st *SScheduledTask) TimerDescription() string {
format := "2006-01-02 15:04:05"
var prefix string
timer := st.STimer
switch timer.Type {
case api.TIMER_TYPE_ONCE:
return fmt.Sprintf("单次 %s触发", timer.StartTime.In(zone).Format(format))
case api.TIMER_TYPE_DAY:
prefix = "每天"
case api.TIMER_TYPE_WEEK:
wds := timer.GetWeekDays()
weekDays := make([]string, len(wds))
for i := range wds {
weekDays[i] = fmt.Sprintf("星期%s", wdsCN[wds[i]])
}
prefix = fmt.Sprintf("每周 【%s】", strings.Join(weekDays, ""))
case api.TIMER_TYPE_MONTH:
mns := timer.GetMonthDays()
monthDays := make([]string, len(mns))
for i := range mns {
monthDays[i] = fmt.Sprintf("%d号", mns[i])
}
prefix = fmt.Sprintf("每月 【%s】", strings.Join(monthDays, ""))
}
return fmt.Sprintf("%s %2d:%2d触发 有效时间为%s至%s", prefix, timer.Hour, timer.Minute, timer.StartTime.In(zone).Format(format), timer.EndTime.In(zone).Format(format))
}
func (st *SScheduledTask) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input apis.PerformEnableInput) bool {
return true
}
func (st *SScheduledTask) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input apis.PerformEnableInput) (jsonutils.JSONObject, error) {
err := db.EnabledPerformEnable(st, ctx, userCred, true)
if err != nil {
return nil, errors.Wrap(err, "EnabledPerformEnable")
}
return nil, nil
}
func (st *SScheduledTask) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input apis.PerformDisableInput) bool {
return true
}
func (st *SScheduledTask) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
input apis.PerformDisableInput) (jsonutils.JSONObject, error) {
err := db.EnabledPerformEnable(st, ctx, userCred, false)
if err != nil {
return nil, errors.Wrap(err, "EnabledPerformEnable")
}
return nil, nil
}
func (st *SScheduledTask) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
st.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
// add label
createFailed := func(reason string) {
st.SetStatus(userCred, api.ST_STATUS_CREATE_FAILED, reason)
logclient.AddActionLogWithContext(ctx, st, logclient.ACT_CREATE, reason, userCred, false)
}
labels, _ := data.GetArray("labels")
for i := range labels {
label, _ := labels[i].GetString()
err := ScheduledTaskLabelManager.Attach(ctx, st.Id, label)
if err != nil {
reason := fmt.Sprintf("unable to attach scheduled task '%s' with '%s'", st.Id, label)
createFailed(reason)
return
}
}
input := api.ScheduledTaskCreateInput{}
err := data.Unmarshal(&input)
if err != nil {
createFailed(err.Error())
return
}
switch st.ScheduledType {
case api.ST_TYPE_TIMING:
st.STimer = STimer{
Type: api.TIMER_TYPE_ONCE,
StartTime: input.Timer.ExecTime,
EndTime: input.Timer.ExecTime,
NextTime: input.Timer.ExecTime,
}
case api.ST_TYPE_CYCLE:
st.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{},
}
st.SetWeekDays(input.CycleTimer.WeekDays)
st.SetMonthDays(input.CycleTimer.MonthDays)
}
st.Update(time.Time{})
st.Status = api.ST_STATUS_READY
st.Enabled = tristate.True
st.TimerDesc = st.TimerDescription()
err = st.GetModelManager().TableSpec().InsertOrUpdate(ctx, st)
if err != nil {
createFailed("update itself")
return
}
logclient.AddActionLogWithContext(ctx, st, logclient.ACT_CREATE, "", userCred, true)
}
func (st *SScheduledTask) ValidateDeleteCondition(ctx context.Context) error {
err := st.SVirtualResourceBase.ValidateDeleteCondition(ctx)
if err != nil {
return err
}
ok, err := st.IsExecuted()
if err != nil {
return err
}
if ok {
return httperrors.NewForbiddenError("This scheduled task is being executed now, please try later")
}
return nil
}
func (st *SScheduledTask) IsExecuted() (bool, error) {
q := ScheduledTaskActivityManager.Query().Equals("status", api.ST_ACTIVITY_STATUS_EXEC).Equals("scheduled_task_id", st.Id)
n, err := q.CountWithError()
if err != nil {
return false, err
}
return n > 0, nil
}
func (st *SScheduledTask) Labels() ([]string, error) {
stLabels, err := st.STLabels()
if err != nil {
return nil, err
}
labels := make([]string, len(stLabels))
for i := range labels {
labels[i] = stLabels[i].Label
}
return labels, nil
}
func (st *SScheduledTask) STLabels() ([]SScheduledTaskLabel, error) {
q := ScheduledTaskLabelManager.Query().Equals("scheduled_task_id", st.Id)
labels := make([]SScheduledTaskLabel, 0, 1)
err := db.FetchModelObjects(ScheduledTaskLabelManager, q, &labels)
return labels, err
}
func (st *SScheduledTask) AllowPerformSetLabels(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ScheduledTaskSetLabelsInput) bool {
return st.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, st, "set-labels")
}
func (st *SScheduledTask) PerformSetLabels(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ScheduledTaskSetLabelsInput) (jsonutils.JSONObject, error) {
nowLabels, err := st.STLabels()
if err != nil {
return nil, err
}
nowLabelMap := make(map[string]*SScheduledTaskLabel, len(nowLabels))
for i := range nowLabels {
nowLabelMap[nowLabels[i].Label] = &nowLabels[i]
}
futureLabelSet := sets.NewString(input.Labels...)
var attachs []string
var detachs []*SScheduledTaskLabel
for label := range futureLabelSet {
if _, ok := nowLabelMap[label]; !ok {
attachs = append(attachs, label)
}
}
for label, stLable := range nowLabelMap {
if !futureLabelSet.Has(label) {
detachs = append(detachs, stLable)
}
}
// attach
for _, label := range attachs {
err := ScheduledTaskLabelManager.Attach(ctx, st.Id, label)
if err != nil {
return nil, err
}
}
// detach
for _, stLabel := range detachs {
err := stLabel.Detach(ctx, userCred)
if err != nil {
return nil, err
}
}
return nil, nil
}
func (st *SScheduledTask) AllowPerformTrigger(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ScheduledTaskTriggerInput) bool {
return st.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, st, "trigger")
}
func (st *SScheduledTask) PerformTrigger(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ScheduledTaskTriggerInput) (jsonutils.JSONObject, error) {
go func() {
log.Infof("start to execute scheduled task '%s'", st.Id)
err := st.Execute(ctx, userCred)
if err != nil {
log.Errorf("fail to execute scheduled task '%s': %s", st.Id, err.Error())
} else {
log.Infof("execute scheduled task '%s' successfully", st.Id)
}
}()
return nil, nil
}
func (st *SScheduledTask) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
err := st.SVirtualResourceBase.CustomizeDelete(ctx, userCred, query, data)
if err != nil {
return err
}
labels, err := st.STLabels()
if err != nil {
return err
}
for i := range labels {
err := labels[i].Delete(ctx, userCred)
if err != nil {
log.Errorf("unbale to delete scheduled task label: %s", err.Error())
}
}
return nil
}
func (st *SScheduledTask) Action(ctx context.Context, userCred mcclient.TokenCredential) SAction {
session := auth.GetSession(ctx, userCred, "", "")
return Action.ResourceOperation(st.ResourceOperation()).Session(session)
}
func (st *SScheduledTask) Execute(ctx context.Context, userCred mcclient.TokenCredential) (err error) {
exec, err := st.IsExecuted()
if err != nil {
return errors.Wrap(err, "unable to check if scheduled task is executed")
}
if exec {
_, err := st.NewActivity(ctx, true)
return err
}
sa, err := st.NewActivity(ctx, false)
if err != nil {
return err
}
over := false
defer func() {
if !over && err != nil {
sa.Fail(err.Error())
}
}()
action := st.Action(ctx, userCred)
// Get All Resource
labels, err := st.Labels()
if err != nil {
return err
}
var ids []string
switch st.LabelType {
case api.ST_LABEL_TAG:
f := false
limit := 1000
opts := options.BaseListOptions{
Details: &f,
Limit: &limit,
Scope: "system",
Tags: labels,
}
res, err := action.List(&opts)
if err != nil {
return err
}
for id := range res {
ids = append(ids, id)
}
case api.ST_LABEL_ID:
ids = labels
}
maxLimit := 20
type result struct {
id string
succeed bool
reason string
}
workerQueue := make(chan struct{}, maxLimit)
results := make([]result, len(ids))
for i, id := range ids {
workerQueue <- struct{}{}
go func(n int, id string) {
ok, reason := action.Apply(id)
results[i] = result{id, ok, reason}
<-workerQueue
}(i, id)
}
// wait all finish
for i := 0; i < maxLimit; i++ {
workerQueue <- struct{}{}
}
failedReasons := make([]string, 0, 1)
succeedIds := make([]string, 0, 1)
for _, ret := range results {
if ret.succeed {
succeedIds = append(succeedIds, ret.id)
continue
}
failedReasons = append(failedReasons, fmt.Sprintf("\t%s: %s", ret.id, ret.reason))
}
if len(failedReasons) == 0 {
sa.Succeed()
return nil
}
if len(failedReasons) == len(ids) {
reason := fmt.Sprintf("All %ss %s failed:\n%s", st.ResourceType, st.Operation, strings.Join(failedReasons, ";\n"))
sa.Fail(reason)
return nil
}
reason := fmt.Sprintf("Some %ss %s successfully:\n\t%s\n\n. Some %ss %s failed:\n%s", st.ResourceType, st.Operation, strings.Join(succeedIds, ";"), st.ResourceType, st.Operation, strings.Join(failedReasons, ";\n"))
sa.PartFail(reason)
return nil
}
func (st *SScheduledTask) NewActivity(ctx context.Context, reject bool) (*SScheduledTaskActivity, error) {
now := time.Now()
sa := &SScheduledTaskActivity{
StartTime: now,
}
sa.Status = api.ST_ACTIVITY_STATUS_EXEC
sa.ScheduledTaskId = st.Id
if reject {
sa.Status = api.ST_ACTIVITY_STATUS_REJECT
sa.EndTime = now
sa.Reason = "This Scheduled Task is being executed now"
}
err := ScheduledTaskActivityManager.TableSpec().Insert(ctx, sa)
if err != nil {
return nil, err
}
sa.SetModelManager(ScheduledTaskActivityManager, sa)
return sa, nil
}
func (st *SScheduledTask) ResourceOperation() ResourceOperation {
return ResourceOperationMap[fmt.Sprintf("%s.%s", st.ResourceType, st.Operation)]
}
type STimeScope struct {
Start time.Time
End time.Time
Median time.Time
}
func (stm *SScheduledTaskManager) timeScope(median time.Time, interval time.Duration) STimeScope {
ri := interval / 2
return STimeScope{
Start: median.Add(-ri),
End: median.Add(ri),
Median: median,
}
}
var timerQueue = make(chan struct{}, cop.Options.ScheduledTaskQueueSize)
func (stm *SScheduledTaskManager) Timer(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
if len(timerQueue) == 0 {
timerQueue = make(chan struct{}, cop.Options.ScheduledTaskQueueSize)
}
// 60 is for fault tolerance
interval := 60 + 30
timeScope := stm.timeScope(time.Now(), time.Duration(interval)*time.Second)
q := stm.Query().Equals("status", api.ST_STATUS_READY).Equals("enabled", true).LT("next_time", timeScope.End).IsFalse("is_expired")
sts := make([]SScheduledTask, 0, 5)
err := db.FetchModelObjects(stm, q, &sts)
if err != nil {
log.Errorf("db.FetchModelObjects error: %s", err.Error())
return
}
log.Debugf("timeScope: start: %s, end: %s", timeScope.Start, timeScope.End)
for i := range sts {
st := sts[i]
timerQueue <- struct{}{}
go func(ctx context.Context) {
defer func() {
<-timerQueue
}()
if st.NextTime.Before(timeScope.Start) {
// For unknown reasons, the scalingTimer did not execute at the specified time
st.Update(timeScope.Start)
// scalingTimer should not exec for now.
if st.NextTime.After(timeScope.End) || st.IsExpired {
err = stm.TableSpec().InsertOrUpdate(ctx, &st)
if err != nil {
log.Errorf("update Scheduled task whose id is %s error: %s", st.Id, err.Error())
}
return
}
}
err := st.Execute(ctx, userCred)
if err != nil {
log.Errorf("unable to execute scheduled task '%s'", st.Id)
}
st.Update(timeScope.End)
err = stm.TableSpec().InsertOrUpdate(ctx, &st)
if err != nil {
log.Errorf("update Scheduled task whose id is %s error: %s", st.Id, err.Error())
}
}(ctx)
}
// wait all finish
}
func init() {
Register(ResourceServer, modules.Servers.ResourceManager)
}
// Modules describe the correspondence between Resource and modulebase.ResourceManager,
// which is equivalent to onecloud resource client.
var Modules = make(map[Resource]modulebase.ResourceManager)
// Every Resource should call Register to register their modulebase.ResourceManager.
func Register(resource Resource, manager modulebase.ResourceManager) {
Modules[resource] = manager
}
// Resoruce describe a onecloud resource, such as:
type Resource string
const (
ResourceServer Resource = api.ST_RESOURCE_SERVER
)
// ResourceOperation describe the operation for onecloud resource like create, update, delete and so on.
type ResourceOperation struct {
Resource Resource
Operation string
StatusSuccess []string
Fail []ResourceOperationFail
}
type ResourceOperationFail struct {
Status string
LogEvent string
}
// It is clearer to write each ResourceOperation as a constant
var (
ServerStart = ResourceOperation{
Resource: ResourceServer,
Operation: api.ST_RESOURCE_OPERATION_START,
StatusSuccess: []string{api.VM_RUNNING},
Fail: []ResourceOperationFail{
{api.VM_START_FAILED, db.ACT_START_FAIL},
},
}
ServerStop = ResourceOperation{
Resource: ResourceServer,
Operation: api.ST_RESOURCE_OPERATION_STOP,
StatusSuccess: []string{api.VM_READY},
Fail: []ResourceOperationFail{
{api.VM_STOP_FAILED, db.ACT_STOP_FAIL},
},
}
ServerRestart = ResourceOperation{
Resource: ResourceServer,
Operation: api.ST_RESOURCE_OPERATION_RESTART,
StatusSuccess: []string{api.VM_RUNNING},
Fail: []ResourceOperationFail{
{api.VM_START_FAILED, db.ACT_START_FAIL},
{api.VM_STOP_FAILED, db.ACT_STOP_FAIL},
},
}
ResourceOperationMap = map[string]ResourceOperation{
fmt.Sprintf("%s.%s", ResourceServer, api.ST_RESOURCE_OPERATION_START): ServerStart,
fmt.Sprintf("%s.%s", ResourceServer, api.ST_RESOURCE_OPERATION_STOP): ServerStop,
fmt.Sprintf("%s.%s", ResourceServer, api.ST_RESOURCE_OPERATION_RESTART): ServerRestart,
}
)
// Action itself is meaningless, a meaningful Action is generated by
// calling Resource, Operation, Session and DefaultParams.
// A example:
// Action.ResourceOperation(ServerStart).Session(...).Apply(...)
var Action = SAction{timeout: 5 * time.Minute}
// SAction encapsulates action to for onecloud resources
type SAction struct {
operation ResourceOperation
session *mcclient.ClientSession
defautParams *jsonutils.JSONDict
timeout time.Duration
}
func (r SAction) ResourceOperation(oper ResourceOperation) SAction {
r.operation = oper
return r
}
func (r SAction) Session(session *mcclient.ClientSession) SAction {
r.session = session
return r
}
func (r SAction) Timeout(time time.Duration) SAction {
r.timeout = time
return r
}
func (r SAction) DefaultParams(dict *jsonutils.JSONDict) SAction {
r.defautParams = dict
return r
}
func (r SAction) List(opts *options.BaseListOptions) (map[string]string, error) {
resourceManager, ok := Modules[r.operation.Resource]
if !ok {
return nil, errors.Errorf("no such resource '%s' in Modules", r.operation.Resource)
}
params, err := options.ListStructToParams(opts)
if err != nil {
return nil, err
}
ret, err := resourceManager.List(r.session, params)
if err != nil {
return nil, err
}
out := make(map[string]string, len(ret.Data))
for i := range ret.Data {
id, _ := ret.Data[i].GetString("id")
name, _ := ret.Data[i].GetString("name")
out[id] = name
}
return out, nil
}
func (r SAction) Apply(id string) (success bool, failReason string) {
success = true
resourceManager, ok := Modules[r.operation.Resource]
if !ok {
return false, fmt.Sprintf("no such resource '%s' in Modules", r.operation.Resource)
}
var requestFunc func(session *mcclient.ClientSession, id string, params *jsonutils.JSONDict) error
action := utils.CamelSplit(r.operation.Operation, "-")
requestFunc = func(session *mcclient.ClientSession, id string, params *jsonutils.JSONDict) error {
if params == nil {
params = jsonutils.NewDict()
}
_, err := resourceManager.PerformAction(session, id, action, params)
return err
}
err := requestFunc(r.session, id, r.defautParams)
if err != nil {
clientErr, _ := err.(*httputils.JSONClientError)
return false, clientErr.Details
}
if len(r.operation.StatusSuccess) == 0 {
return true, ""
}
// wait for status
timer := time.NewTimer(r.timeout)
ticker := time.NewTicker(10 * time.Second)
defer func() {
ticker.Stop()
timer.Stop()
}()
for {
select {
default:
ret, e := resourceManager.GetSpecific(r.session, id, "status", nil)
if e != nil {
log.Errorf("fail to exec resouce(%s.%s).GetStatus: %s", r.operation.Resource, id, e.Error())
<-ticker.C
continue
}
status, _ := ret.GetString("status")
if utils.IsInStringArray(status, r.operation.StatusSuccess) {
return
}
for _, fail := range r.operation.Fail {
if status != fail.Status {
continue
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(id), "obj_id")
params.Add(jsonutils.NewStringArray([]string{fail.LogEvent}), "action")
params.Add(jsonutils.NewInt(1), "limit")
events, err := modules.Logs.List(r.session, params)
if err != nil {
log.Errorf("Logs.List failed: %s", err.Error())
<-ticker.C
continue
}
if len(events.Data) == 0 {
log.Errorf("These is no opslog about action '%s' for %s.%s: %s", fail.LogEvent, r.operation.Resource, id, err.Error())
<-ticker.C
continue
}
reason, _ := events.Data[0].GetString("notes")
return false, reason
}
<-ticker.C
case <-timer.C:
log.Errorf("timeout(%s) to exec resource(%s.%s).%s", r.timeout.String(), r.operation.Resource, id, r.operation.Operation)
return false, "timeout"
}
}
}

View File

@@ -0,0 +1,173 @@
// 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"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/sqlchemy"
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/rbacutils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
var ScheduledTaskActivityManager *SScheduledTaskActivityManager
func init() {
ScheduledTaskActivityManager = &SScheduledTaskActivityManager{
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
SScheduledTaskActivity{},
"scheduledtaskactivities_tbl",
"scheduledtaskactivity",
"scheduledtaskactivities",
),
}
ScheduledTaskActivityManager.SetVirtualObject(ScheduledTaskActivityManager)
}
// +onecloud:swagger-gen-model-singular=scheduledtaskactivity
// +onecloud:swagger-gen-model-singular=scheduledtaskactivities
type SScheduledTaskActivityManager struct {
db.SStatusStandaloneResourceBaseManager
}
type SScheduledTaskActivity struct {
db.SStatusStandaloneResourceBase
ScheduledTaskId string `width:"36" charset:"ascii" nullable:"true" list:"user" index:"true"`
StartTime time.Time `list:"user"`
EndTime time.Time `list:"user"`
Reason string `charset:"utf8" list:"user"`
}
func (sam *SScheduledTaskActivityManager) InitializeData() error {
sas := make([]SScheduledTaskActivity, 0, 10)
q := ScheduledTaskActivityManager.Query().Equals("status", api.ST_ACTIVITY_STATUS_EXEC)
err := db.FetchModelObjects(ScheduledTaskActivityManager, q, &sas)
if err != nil {
return err
}
for i := range sas {
err := sas[i].Fail("As the service restarts, the status becomes unknown")
if err != nil {
return err
}
}
return nil
}
func (sam *SScheduledTaskActivityManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
return sam.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
}
func (sam *SScheduledTaskActivityManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ScheduledTaskActivityListInput) (*sqlchemy.SQuery, error) {
return sam.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput)
}
func (sam *SScheduledTaskActivityManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.ScheduledTaskActivityDetails {
rows := make([]api.ScheduledTaskActivityDetails, len(objs))
statusRows := sam.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i].StatusStandaloneResourceDetails = statusRows[i]
}
return rows
}
func (sa *SScheduledTaskActivity) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.ScheduledTaskActivityDetails, error) {
return api.ScheduledTaskActivityDetails{}, nil
}
func (sam *SScheduledTaskActivityManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ScheduledTaskActivityListInput) (*sqlchemy.SQuery, error) {
q, err := sam.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput)
if err != nil {
return nil, err
}
q = q.Desc("start_time").Desc("end_time")
if len(input.ScheduledTask) == 0 {
return nil, httperrors.NewInputParameterError("need scheduled task")
}
task, err := ScheduledTaskManager.FetchByIdOrName(userCred, input.ScheduledTask)
if err != nil {
return nil, err
}
q = q.Equals("scheduled_task_id", task.GetId())
return q, nil
}
func (sam *SScheduledTaskActivityManager) NamespaceScope() rbacutils.TRbacScope {
return rbacutils.ScopeProject
}
func (sam *SScheduledTaskActivityManager) ResourceScope() rbacutils.TRbacScope {
return rbacutils.ScopeProject
}
func (sam *SScheduledTaskActivityManager) FileterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery {
if owner != nil {
switch scope {
case rbacutils.ScopeProject, rbacutils.ScopeDomain:
scheduledTaskQ := ScheduledTaskManager.Query("id", "domain_id").SubQuery()
q = q.Join(scheduledTaskQ, sqlchemy.Equals(q.Field("scheduled_task_id"), scheduledTaskQ.Field("id")))
q = q.Filter(sqlchemy.Equals(scheduledTaskQ.Field("domain_id"), owner.GetProjectDomainId()))
}
}
return q
}
func (sam *SScheduledTaskActivityManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
return db.FetchDomainInfo(ctx, data)
}
func (sa *SScheduledTaskActivity) GetOwnerId() mcclient.IIdentityProvider {
obj, _ := ScheduledTaskManager.FetchById(sa.ScheduledTaskId)
if obj == nil {
return nil
}
return obj.GetOwnerId()
}
func (sa *SScheduledTaskActivity) SetResult(status, reason string) error {
_, err := db.Update(sa, func() error {
sa.Status = status
sa.Reason = reason
sa.EndTime = time.Now()
return nil
})
return err
}
func (sa *SScheduledTaskActivity) Fail(reason string) error {
return sa.SetResult(api.ST_ACTIVITY_STATUS_FAILED, reason)
}
func (sa *SScheduledTaskActivity) Succeed() error {
return sa.SetResult(api.ST_ACTIVITY_STATUS_SUCCEED, "")
}
func (sa *SScheduledTaskActivity) PartFail(reason string) error {
return sa.SetResult(api.ST_ACTIVITY_STATUS_PART_SUCCEED, reason)
}

View File

@@ -0,0 +1,61 @@
// 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"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
var ScheduledTaskLabelManager *SScheduledTaskLabelManager
func init() {
db.InitManager(func() {
ScheduledTaskLabelManager = &SScheduledTaskLabelManager{
SVirtualJointResourceBaseManager: db.NewVirtualJointResourceBaseManager(
SScheduledTaskLabel{},
"scheduledtasklabels_tbl",
"scheduledtasklabel",
"scheduledtasklabels",
ScheduledTaskManager,
GuestManager,
),
}
})
}
type SScheduledTaskLabelManager struct {
db.SVirtualJointResourceBaseManager
}
type SScheduledTaskLabel struct {
db.SVirtualJointResourceBase
ScheduledTaskId string `width:"36" charset:"ascii" nullable:"false" index:"true"`
Label string `width:"36" charset:"ascii" nullable:"false" index:"true"`
}
func (slm *SScheduledTaskLabelManager) Attach(ctx context.Context, taskId, label string) error {
sl := &SScheduledTaskLabel{
ScheduledTaskId: taskId,
Label: label,
}
return slm.TableSpec().Insert(ctx, sl)
}
func (sl *SScheduledTaskLabel) Detach(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DetachJoint(ctx, userCred, sl)
}

191
pkg/compute/models/timer.go Normal file
View File

@@ -0,0 +1,191 @@
// 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 (
"fmt"
"sort"
"time"
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/util/bitmap"
)
type STimer struct {
// Cycle type
Type string `width:"8" charset:"ascii"`
// 0-59
Minute int `nullable:"false"`
// 0-23
Hour int `nullable:"false"`
// 0-7 1 is Monday 0 is unlimited
WeekDays uint8 `nullable:"false"`
// 0-31 0 is unlimited
MonthDays uint32 `nullable:"false"`
// StartTime represent the start time of this timer
StartTime time.Time
// EndTime represent deadline of this timer
EndTime time.Time
// NextTime represent the time timer should bell
NextTime time.Time `index:"true"`
IsExpired bool
}
// Update will update the SScalingTimer
func (st *STimer) Update(now time.Time) {
if now.IsZero() {
now = time.Now()
}
if !now.Before(st.EndTime) {
st.IsExpired = true
return
}
if now.Before(st.StartTime) {
now = st.StartTime
}
if !st.NextTime.Before(now) {
return
}
newNextTime := time.Date(now.Year(), now.Month(), now.Day(), st.Hour, st.Minute, 0, 0, now.Location())
if now.After(newNextTime) {
newNextTime = newNextTime.AddDate(0, 0, 1)
}
switch {
case st.WeekDays != 0:
// week
nowDay, weekdays := int(newNextTime.Weekday()), st.GetWeekDays()
if nowDay == 0 {
nowDay = 7
}
// weekdays[0]+7 is for the case that all time nodes has been missed in this week
weekdays = append(weekdays, weekdays[0]+7)
index := sort.SearchInts(weekdays, nowDay)
newNextTime = newNextTime.AddDate(0, 0, weekdays[index]-nowDay)
case st.MonthDays != 0:
// month
monthdays := st.GetMonthDays()
suitTime := newNextTime
for {
day := suitTime.Day()
index := sort.SearchInts(monthdays, day)
if index == len(monthdays) || monthdays[index] > st.MonthDaySum(suitTime) {
// set suitTime as the first day of next month
suitTime = suitTime.AddDate(0, 1, -suitTime.Day()+1)
continue
}
newNextTime = time.Date(suitTime.Year(), suitTime.Month(), monthdays[index], suitTime.Hour(),
suitTime.Minute(), 0, 0, suitTime.Location())
break
}
default:
// day
}
log.Debugf("The final NextTime: %s", newNextTime)
st.NextTime = newNextTime
if st.NextTime.After(st.EndTime) {
st.IsExpired = true
}
}
// MonthDaySum calculate the number of month's days
func (st *STimer) MonthDaySum(t time.Time) int {
year, month := t.Year(), t.Month()
monthDays := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
if month != 2 {
return monthDays[2]
}
if year%4 != 0 || (year%100 == 0 && year%400 != 0) {
return 28
}
return 29
}
func (st *STimer) GetWeekDays() []int {
return bitmap.Uint2IntArray(uint32(st.WeekDays))
}
func (st *STimer) GetMonthDays() []int {
return bitmap.Uint2IntArray(st.MonthDays)
}
func (st *STimer) SetWeekDays(days []int) {
st.WeekDays = uint8(bitmap.IntArray2Uint(days))
}
func (st *STimer) SetMonthDays(days []int) {
st.MonthDays = bitmap.IntArray2Uint(days)
}
func (st *STimer) TimerDetails() api.TimerDetails {
return api.TimerDetails{ExecTime: st.EndTime}
}
func (st *STimer) CycleTimerDetails() api.CycleTimerDetails {
out := api.CycleTimerDetails{
Minute: st.Minute,
Hour: st.Hour,
WeekDays: st.GetWeekDays(),
MonthDays: st.GetMonthDays(),
StartTime: st.StartTime,
EndTime: st.EndTime,
CycleType: st.Type,
}
return out
}
func checkTimerCreateInput(in api.TimerCreateInput) (api.TimerCreateInput, error) {
now := time.Now()
if now.After(in.ExecTime) {
return in, fmt.Errorf("exec_time is earlier than now")
}
return in, nil
}
func checkCycleTimerCreateInput(in api.CycleTimerCreateInput) (api.CycleTimerCreateInput, error) {
now := time.Now()
if in.Minute < 0 || in.Minute > 59 {
return in, fmt.Errorf("minute should between 0 and 59")
}
if in.Hour < 0 || in.Hour > 23 {
return in, fmt.Errorf("hour should between 0 and 23")
}
switch in.CycleType {
case api.TIMER_TYPE_DAY:
in.WeekDays = []int{}
in.MonthDays = []int{}
case api.TIMER_TYPE_WEEK:
if len(in.WeekDays) == 0 {
return in, fmt.Errorf("week_days should not be empty")
}
in.MonthDays = []int{}
case api.TIMER_TYPE_MONTH:
if len(in.MonthDays) == 0 {
return in, fmt.Errorf("month_days should not be empty")
}
in.WeekDays = []int{}
default:
return in, fmt.Errorf("unkown cycle type %s", in.CycleType)
}
if now.After(in.EndTime) {
return in, fmt.Errorf("end_time is earlier than now")
}
return in, nil
}

View File

@@ -136,6 +136,8 @@ type ComputeOptions struct {
GuestTemplateCheckInterval int `help:"interval between two consecutive inspections of Guest Template in hour unit" default:"12"`
ScheduledTaskQueueSize int `help:"the maximum number of scheduled tasks that are being executed simultaneously" default:"100"`
SCapabilityOptions
SASControllerOptions
common_options.CommonOptions

View File

@@ -88,6 +88,8 @@ func InitHandlers(app *appsrv.Application) {
models.ScalingAlarmManager,
models.ScalingGroupGuestManager,
models.ScalingGroupNetworkManager,
models.ScheduledTaskLabelManager,
} {
db.RegisterModelManager(manager)
}
@@ -181,6 +183,9 @@ func InitHandlers(app *appsrv.Application) {
models.ScalingActivityManager,
models.PolicyDefinitionManager,
models.PolicyAssignmentManager,
models.ScheduledTaskManager,
models.ScheduledTaskActivityManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)

View File

@@ -145,6 +145,8 @@ func StartService() {
cron.AddJobEveryFewDays("StorageSnapshotsRecycle", 1, 2, 0, 0, models.StorageManager.StorageSnapshotsRecycle, false)
cron.AddJobEveryFewHour("InspectAllTemplate", 1, 0, 0, models.GuestTemplateManager.InspectAllTemplate, true)
cron.AddJobAtIntervalsWithStartRun("ScheduledTaskCheck", time.Duration(60)*time.Second, models.ScheduledTaskManager.Timer, true)
go cron.Start2(ctx, electObj)
// init auto scaling controller

View File

@@ -194,6 +194,7 @@ func (self *GuestDeleteTask) OnSyncConfigComplete(ctx context.Context, obj db.IS
log.Debugf("XXXXXXX Do guest pending delete... XXXXXXX")
// pending detach
guest.PendingDetachScalingGroup()
guest.DetachScheduledTask(ctx, self.UserCred)
guestStatus, _ := self.Params.GetString("guest_status")
if !utils.IsInStringArray(guestStatus, []string{
api.VM_SCHEDULE_FAILED, api.VM_NETWORK_FAILED, api.VM_DISK_FAILED,

View File

@@ -586,6 +586,7 @@ func (asc *SASController) checkAllServer(session *mcclient.ClientSession, guestI
ret, e := modules.Servers.GetSpecific(session, id, "status", nil)
if e != nil {
log.Errorf("Servers.GetSpecific failed: %s", e)
<-ticker.C
continue
}
log.Debugf("ret from GetSpecific: %s", ret.String())

View File

@@ -0,0 +1,33 @@
// 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 modules
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
var (
ScheduledTask modulebase.ResourceManager
ScheduledTaskActivity modulebase.ResourceManager
)
func init() {
ScheduledTask = NewComputeManager("scheduledtask", "scheduledtasks",
[]string{"ID", "Name", "Scheduled_Type", "Timer", "Cycle_Timer", "Resource_Type", "Operation", "Label_Type", "Labels", "Timer_Desc"}, []string{},
)
ScheduledTaskActivity = NewComputeManager("scheudledtaskactivity", "scheduledtaskactivities",
[]string{"ID", "Status", "Scheduled_Task_Id", "Start_Time", "End_Time", "Reason"}, []string{},
)
registerCompute(&ScheduledTask)
registerCompute(&ScheduledTaskActivity)
}