mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Add action type *dynamic_action_plan
This commit is contained in:
committed by
Dan Christian Bogos
parent
ccdf3ef1f1
commit
33a47f663c
@@ -734,60 +734,7 @@ func (apierSv1 *APIerSv1) GetActions(ctx *context.Context, actsId *string, reply
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrSetActionPlan struct {
|
||||
Id string // Profile id
|
||||
ActionPlan []*AttrActionPlan // Set of actions this Actions profile will perform
|
||||
Overwrite bool // If previously defined, will be overwritten
|
||||
ReloadScheduler bool // Enables automatic reload of the scheduler (eg: useful when adding a single action timing)
|
||||
}
|
||||
|
||||
type AttrActionPlan struct {
|
||||
ActionsId string // Actions id
|
||||
TimingID string // timingID is used to specify the ID of the timing for a corner case ( e.g. *monthly_estimated )
|
||||
Years string // semicolon separated list of years this timing is valid on, *any or empty supported
|
||||
Months string // semicolon separated list of months this timing is valid on, *any or empty supported
|
||||
MonthDays string // semicolon separated list of month's days this timing is valid on, *any or empty supported
|
||||
WeekDays string // semicolon separated list of week day names this timing is valid on *any or empty supported
|
||||
Time string // String representing the time this timing starts on, *asap supported
|
||||
Weight float64 // Binding's weight
|
||||
}
|
||||
|
||||
func (attr *AttrActionPlan) getRITiming(dm *engine.DataManager) (timing *engine.RITiming, err error) {
|
||||
if dfltTiming, isDefault := checkDefaultTiming(attr.Time); isDefault {
|
||||
return dfltTiming, nil
|
||||
}
|
||||
timing = new(engine.RITiming)
|
||||
|
||||
if attr.TimingID != utils.EmptyString &&
|
||||
!strings.HasPrefix(attr.TimingID, utils.Meta) { // in case of dynamic timing
|
||||
if dbTiming, err := dm.GetTiming(attr.TimingID, false, utils.NonTransactional); err != nil {
|
||||
if err != utils.ErrNotFound { // if not found let the user to populate all the timings values
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
timing.ID = dbTiming.ID
|
||||
timing.Years = dbTiming.Years
|
||||
timing.Months = dbTiming.Months
|
||||
timing.MonthDays = dbTiming.MonthDays
|
||||
timing.WeekDays = dbTiming.WeekDays
|
||||
timing.StartTime = dbTiming.StartTime
|
||||
timing.EndTime = dbTiming.EndTime
|
||||
}
|
||||
}
|
||||
timing.ID = attr.TimingID
|
||||
timing.Years.Parse(attr.Years, ";")
|
||||
timing.Months.Parse(attr.Months, ";")
|
||||
timing.MonthDays.Parse(attr.MonthDays, ";")
|
||||
timing.WeekDays.Parse(attr.WeekDays, ";")
|
||||
if !verifyFormat(attr.Time) {
|
||||
err = fmt.Errorf("%s:%s", utils.ErrUnsupportedFormat.Error(), attr.Time)
|
||||
return
|
||||
}
|
||||
timing.StartTime = attr.Time
|
||||
return
|
||||
}
|
||||
|
||||
func (apierSv1 *APIerSv1) SetActionPlan(ctx *context.Context, attrs *AttrSetActionPlan, reply *string) (err error) {
|
||||
func (apierSv1 *APIerSv1) SetActionPlan(ctx *context.Context, attrs *engine.AttrSetActionPlan, reply *string) (err error) {
|
||||
if missing := utils.MissingStructFields(attrs, []string{"Id", "ActionPlan"}); len(missing) != 0 {
|
||||
return utils.NewErrMandatoryIeMissing(missing...)
|
||||
}
|
||||
@@ -815,7 +762,7 @@ func (apierSv1 *APIerSv1) SetActionPlan(ctx *context.Context, attrs *AttrSetActi
|
||||
} else if !exists {
|
||||
return fmt.Errorf("%s:%s", utils.ErrBrokenReference.Error(), apiAtm.ActionsId)
|
||||
}
|
||||
timing, err := apiAtm.getRITiming(apierSv1.DataManager)
|
||||
timing, err := apiAtm.GetRITiming(apierSv1.DataManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -868,119 +815,6 @@ func (apierSv1 *APIerSv1) SetActionPlan(ctx *context.Context, attrs *AttrSetActi
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyFormat(tStr string) bool {
|
||||
if tStr == utils.EmptyString ||
|
||||
tStr == utils.MetaASAP {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(tStr) > 8 { // hh:mm:ss
|
||||
return false
|
||||
}
|
||||
if a := strings.Split(tStr, utils.InInFieldSep); len(a) != 3 {
|
||||
return false
|
||||
} else {
|
||||
if _, err := strconv.Atoi(a[0]); err != nil {
|
||||
return false
|
||||
} else if _, err := strconv.Atoi(a[1]); err != nil {
|
||||
return false
|
||||
} else if _, err := strconv.Atoi(a[2]); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkDefaultTiming will check the tStr if it's of the the default timings ( the same as in TPReader )
|
||||
// and will compute it properly
|
||||
func checkDefaultTiming(tStr string) (rTm *engine.RITiming, isDefault bool) {
|
||||
currentTime := time.Now()
|
||||
fmtTime := currentTime.Format("15:04:05")
|
||||
switch tStr {
|
||||
case utils.MetaEveryMinute:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaEveryMinute,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: utils.ConcatenatedKey(utils.Meta, utils.Meta, strconv.Itoa(currentTime.Second())),
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaHourly:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaHourly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: utils.ConcatenatedKey(utils.Meta, strconv.Itoa(currentTime.Minute()), strconv.Itoa(currentTime.Second())),
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaDaily:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaDaily,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: ""}, true
|
||||
case utils.MetaWeekly:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaWeekly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{currentTime.Weekday()},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaMonthly:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaMonthly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{currentTime.Day()},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaMonthlyEstimated:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaMonthlyEstimated,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{currentTime.Day()},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaMonthEnd:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaMonthEnd,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{-1},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaYearly:
|
||||
return &engine.RITiming{
|
||||
ID: utils.MetaYearly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{currentTime.Month()},
|
||||
MonthDays: utils.MonthDays{currentTime.Day()},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
type AttrGetActionPlan struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/apier/v1"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ func init() {
|
||||
c := &CmdSetActionPlan{
|
||||
name: "actionplan_set",
|
||||
rpcMethod: utils.APIerSv1SetActionPlan,
|
||||
rpcParams: &v1.AttrSetActionPlan{},
|
||||
rpcParams: &engine.AttrSetActionPlan{},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
@@ -37,7 +37,7 @@ func init() {
|
||||
type CmdSetActionPlan struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *v1.AttrSetActionPlan
|
||||
rpcParams *engine.AttrSetActionPlan
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (self *CmdSetActionPlan) RpcMethod() string {
|
||||
|
||||
func (self *CmdSetActionPlan) RpcParams(reset bool) any {
|
||||
if reset || self.rpcParams == nil {
|
||||
self.rpcParams = &v1.AttrSetActionPlan{}
|
||||
self.rpcParams = &engine.AttrSetActionPlan{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
105
engine/action.go
105
engine/action.go
@@ -125,14 +125,19 @@ func newActionConnCfg(source, action string, cfg *config.CGRConfig) ActionConnCf
|
||||
utils.MetaAlterSessions,
|
||||
utils.MetaForceDisconnectSessions,
|
||||
}
|
||||
dynamicActions := []string{
|
||||
utils.MetaDynamicThreshold, utils.MetaDynamicStats,
|
||||
utils.MetaDynamicAttribute, utils.MetaDynamicActionPlan,
|
||||
utils.MetaDynamicAction, utils.MetaDynamicDestination,
|
||||
utils.MetaDynamicAccountAction,
|
||||
}
|
||||
act := ActionConnCfg{}
|
||||
switch source {
|
||||
case utils.ThresholdS:
|
||||
switch {
|
||||
case slices.Contains(sessionActions, action):
|
||||
act.ConnIDs = cfg.ThresholdSCfg().SessionSConns
|
||||
case utils.MetaDynamicThreshold == action || utils.MetaDynamicStats == action ||
|
||||
utils.MetaDynamicAttribute == action:
|
||||
case slices.Contains(dynamicActions, action):
|
||||
act.ConnIDs = cfg.ThresholdSCfg().ApierSConns
|
||||
}
|
||||
case utils.RALs:
|
||||
@@ -187,6 +192,7 @@ func init() {
|
||||
actionFuncMap[utils.MetaDynamicThreshold] = dynamicThreshold
|
||||
actionFuncMap[utils.MetaDynamicStats] = dynamicStats
|
||||
actionFuncMap[utils.MetaDynamicAttribute] = dynamicAttribute
|
||||
actionFuncMap[utils.MetaDynamicActionPlan] = dynamicActionPlan
|
||||
}
|
||||
|
||||
func getActionFunc(typ string) (f actionTypeFunc, exists bool) {
|
||||
@@ -990,7 +996,7 @@ func alterSessionsAction(_ *Account, act *Action, _ Actions, _ *FilterS, _ any,
|
||||
// Parse action parameters based on the predefined format.
|
||||
params := strings.Split(act.ExtraParameters, ";")
|
||||
if len(params) != 5 {
|
||||
return errors.New("invalid number of parameters; expected 5")
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 5", len(params)))
|
||||
}
|
||||
|
||||
// If conversion fails, limit will default to 0.
|
||||
@@ -1035,7 +1041,7 @@ func forceDisconnectSessionsAction(_ *Account, act *Action, _ Actions, _ *Filter
|
||||
// Parse action parameters based on the predefined format.
|
||||
params := strings.Split(act.ExtraParameters, ";")
|
||||
if len(params) != 5 {
|
||||
return errors.New("invalid number of parameters; expected 5")
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 5", len(params)))
|
||||
}
|
||||
|
||||
// If conversion fails, limit will default to 0.
|
||||
@@ -1468,7 +1474,7 @@ func dynamicThreshold(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
// Parse action parameters based on the predefined format.
|
||||
params := strings.Split(act.ExtraParameters, utils.InfieldSep)
|
||||
if len(params) != 12 {
|
||||
return errors.New("invalid number of parameters; expected 12")
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 12", len(params)))
|
||||
}
|
||||
// parse dynamic parameters
|
||||
for i := range params {
|
||||
@@ -1597,7 +1603,7 @@ func dynamicStats(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
// Parse action parameters based on the predefined format.
|
||||
params := strings.Split(act.ExtraParameters, utils.InfieldSep)
|
||||
if len(params) != 14 {
|
||||
return errors.New("invalid number of parameters; expected 14")
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 14", len(params)))
|
||||
}
|
||||
// parse dynamic parameters
|
||||
for i := range params {
|
||||
@@ -1817,3 +1823,90 @@ func dynamicAttribute(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
var reply string
|
||||
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetAttributeProfile, attrP, &reply)
|
||||
}
|
||||
|
||||
// dynamicActionPlan processes the `ExtraParameters` field from the action to construct an ActionPlan
|
||||
//
|
||||
// The ExtraParameters field format is expected as follows:
|
||||
//
|
||||
// 0 Id: string
|
||||
// 1 ActionsId: string
|
||||
// 2 TimingId: string
|
||||
// 3 Weight: float
|
||||
// 4 Overwrite: bool
|
||||
//
|
||||
// Parameters are separated by ";" and must be provided in the specified order.
|
||||
func dynamicActionPlan(_ *Account, act *Action, _ Actions, _ *FilterS, ev any,
|
||||
_ SharedActionsData, connCfg ActionConnCfg) (err error) {
|
||||
cgrEv, canCast := ev.(*utils.CGREvent)
|
||||
if !canCast {
|
||||
return errors.New("Couldn't cast event to CGREvent")
|
||||
}
|
||||
dP := utils.MapStorage{ // create DataProvider from event
|
||||
utils.MetaReq: cgrEv.Event,
|
||||
utils.MetaTenant: cgrEv.Tenant,
|
||||
utils.MetaNow: time.Now(),
|
||||
utils.MetaOpts: cgrEv.APIOpts,
|
||||
}
|
||||
// Parse action parameters based on the predefined format.
|
||||
params := strings.Split(act.ExtraParameters, utils.InfieldSep)
|
||||
if len(params) != 5 {
|
||||
return errors.New(fmt.Sprintf("invalid number of parameters <%d> expected 5", len(params)))
|
||||
}
|
||||
// parse dynamic parameters
|
||||
for i := range params {
|
||||
if params[i], err = utils.ParseParamForDataProvider(params[i], dP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Prepare request arguments based on provided parameters.
|
||||
ap := &AttrSetActionPlan{
|
||||
Id: params[0],
|
||||
ReloadScheduler: true,
|
||||
}
|
||||
// populate ActionPlan's ActionsId
|
||||
if params[1] == utils.EmptyString {
|
||||
return fmt.Errorf("empty ActionsId for <%s> dynamic_action_plan", params[0])
|
||||
}
|
||||
// Make sure ActionsId exists in DataDB
|
||||
var actsRply []*utils.TPAction
|
||||
if err := connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1GetActions, params[1], &actsRply); err != nil {
|
||||
return err
|
||||
}
|
||||
ap.ActionPlan = append(ap.ActionPlan, &AttrActionPlan{})
|
||||
ap.ActionPlan[0].ActionsId = params[1]
|
||||
if params[2] != utils.EmptyString {
|
||||
// Make sure TimingID exists in DataDB and use it for the action plan
|
||||
var tpTiming utils.TPTiming
|
||||
if err := connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1GetTiming, &utils.ArgsGetTimingID{ID: params[2]}, &tpTiming); err != nil {
|
||||
return err
|
||||
}
|
||||
ap.ActionPlan[0].TimingID = tpTiming.ID
|
||||
ap.ActionPlan[0].Years = tpTiming.Years.Serialize(";")
|
||||
ap.ActionPlan[0].Months = tpTiming.Months.Serialize(";")
|
||||
ap.ActionPlan[0].MonthDays = tpTiming.MonthDays.Serialize(";")
|
||||
ap.ActionPlan[0].WeekDays = tpTiming.WeekDays.Serialize(";")
|
||||
if tpTiming.EndTime != utils.EmptyString {
|
||||
ap.ActionPlan[0].Time = utils.InfieldJoin(tpTiming.StartTime, tpTiming.EndTime)
|
||||
} else {
|
||||
ap.ActionPlan[0].Time = tpTiming.StartTime
|
||||
}
|
||||
}
|
||||
// populate ActionPlan's Weight
|
||||
if params[3] != utils.EmptyString {
|
||||
ap.ActionPlan[0].Weight, err = strconv.ParseFloat(params[3], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// populate ActionPlan's Overwrite
|
||||
if params[4] != utils.EmptyString {
|
||||
ap.Overwrite, err = strconv.ParseBool(params[4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// create the ActionPlan based on the populated parameters
|
||||
var reply string
|
||||
return connMgr.Call(context.Background(), connCfg.ConnIDs, utils.APIerSv1SetActionPlan, ap, &reply)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ package engine
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/config"
|
||||
@@ -129,6 +131,14 @@ func (at *ActionTiming) GetNextStartTime(refTime time.Time) time.Time {
|
||||
if !at.stCache.IsZero() {
|
||||
return at.stCache
|
||||
}
|
||||
// Put action schedule time in stCache for 1 time actions
|
||||
if at.Timing != nil && at.Timing.Timing != nil &&
|
||||
strings.HasPrefix(at.Timing.Timing.StartTime, utils.PlusChar) {
|
||||
tmStrTmp, _ := time.ParseDuration(strings.TrimPrefix(
|
||||
at.Timing.Timing.StartTime, utils.PlusChar))
|
||||
at.stCache = time.Now().Add(tmStrTmp)
|
||||
return at.stCache
|
||||
}
|
||||
rateIvl := at.Timing
|
||||
if rateIvl == nil || rateIvl.Timing == nil {
|
||||
return time.Time{}
|
||||
@@ -174,6 +184,10 @@ func (at *ActionTiming) GetNextStartTime(refTime time.Time) time.Time {
|
||||
}
|
||||
|
||||
func (at *ActionTiming) ResetStartTimeCache() {
|
||||
if at.Timing != nil && at.Timing.Timing != nil && strings.HasPrefix(at.Timing.Timing.StartTime,
|
||||
utils.PlusChar) {
|
||||
return // dont reset time for 1 time action plans starting with "+"
|
||||
}
|
||||
at.stCache = time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
@@ -375,3 +389,169 @@ func (atpl ActionTimingWeightOnlyPriorityList) Less(i, j int) bool {
|
||||
func (atpl ActionTimingWeightOnlyPriorityList) Sort() {
|
||||
sort.Sort(atpl)
|
||||
}
|
||||
|
||||
type AttrSetActionPlan struct {
|
||||
Id string // Profile id
|
||||
ActionPlan []*AttrActionPlan // Set of actions this Actions profile will perform
|
||||
Overwrite bool // If previously defined, will be overwritten
|
||||
ReloadScheduler bool // Enables automatic reload of the scheduler (eg: useful when adding a single action timing)
|
||||
}
|
||||
|
||||
type AttrActionPlan struct {
|
||||
ActionsId string // Actions id
|
||||
TimingID string // timingID is used to specify the ID of the timing for a corner case ( e.g. *monthly_estimated )
|
||||
Years string // semicolon separated list of years this timing is valid on, *any or empty supported
|
||||
Months string // semicolon separated list of months this timing is valid on, *any or empty supported
|
||||
MonthDays string // semicolon separated list of month's days this timing is valid on, *any or empty supported
|
||||
WeekDays string // semicolon separated list of week day names this timing is valid on *any or empty supported
|
||||
Time string // String representing the time this timing starts on, *asap supported
|
||||
Weight float64 // Binding's weight
|
||||
}
|
||||
|
||||
func (attr *AttrActionPlan) GetRITiming(dm *DataManager) (timing *RITiming, err error) {
|
||||
if dfltTiming, isDefault := checkDefaultTiming(attr.Time); isDefault {
|
||||
return dfltTiming, nil
|
||||
}
|
||||
timing = new(RITiming)
|
||||
|
||||
if attr.TimingID != utils.EmptyString &&
|
||||
!strings.HasPrefix(attr.TimingID, utils.Meta) { // in case of dynamic timing
|
||||
if dbTiming, err := dm.GetTiming(attr.TimingID, false, utils.NonTransactional); err != nil {
|
||||
if err != utils.ErrNotFound { // if not found let the user to populate all the timings values
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
timing.ID = dbTiming.ID
|
||||
timing.Years = dbTiming.Years
|
||||
timing.Months = dbTiming.Months
|
||||
timing.MonthDays = dbTiming.MonthDays
|
||||
timing.WeekDays = dbTiming.WeekDays
|
||||
timing.StartTime = dbTiming.StartTime
|
||||
timing.EndTime = dbTiming.EndTime
|
||||
}
|
||||
}
|
||||
timing.ID = attr.TimingID
|
||||
timing.Years.Parse(attr.Years, ";")
|
||||
timing.Months.Parse(attr.Months, ";")
|
||||
timing.MonthDays.Parse(attr.MonthDays, ";")
|
||||
timing.WeekDays.Parse(attr.WeekDays, ";")
|
||||
if !verifyFormat(attr.Time) {
|
||||
err = fmt.Errorf("%s:%s", utils.ErrUnsupportedFormat.Error(), attr.Time)
|
||||
return
|
||||
}
|
||||
timing.StartTime = attr.Time
|
||||
return
|
||||
}
|
||||
|
||||
// checkDefaultTiming will check the tStr if it's of the the default timings ( the same as in TPReader )
|
||||
// and will compute it properly
|
||||
func checkDefaultTiming(tStr string) (rTm *RITiming, isDefault bool) {
|
||||
currentTime := time.Now()
|
||||
fmtTime := currentTime.Format("15:04:05")
|
||||
switch tStr {
|
||||
case utils.MetaEveryMinute:
|
||||
return &RITiming{
|
||||
ID: utils.MetaEveryMinute,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: utils.ConcatenatedKey(utils.Meta, utils.Meta, strconv.Itoa(currentTime.Second())),
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaHourly:
|
||||
return &RITiming{
|
||||
ID: utils.MetaHourly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: utils.ConcatenatedKey(utils.Meta, strconv.Itoa(currentTime.Minute()), strconv.Itoa(currentTime.Second())),
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaDaily:
|
||||
return &RITiming{
|
||||
ID: utils.MetaDaily,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: ""}, true
|
||||
case utils.MetaWeekly:
|
||||
return &RITiming{
|
||||
ID: utils.MetaWeekly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{},
|
||||
WeekDays: utils.WeekDays{currentTime.Weekday()},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaMonthly:
|
||||
return &RITiming{
|
||||
ID: utils.MetaMonthly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{currentTime.Day()},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaMonthlyEstimated:
|
||||
return &RITiming{
|
||||
ID: utils.MetaMonthlyEstimated,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{currentTime.Day()},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaMonthEnd:
|
||||
return &RITiming{
|
||||
ID: utils.MetaMonthEnd,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{},
|
||||
MonthDays: utils.MonthDays{-1},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
case utils.MetaYearly:
|
||||
return &RITiming{
|
||||
ID: utils.MetaYearly,
|
||||
Years: utils.Years{},
|
||||
Months: utils.Months{currentTime.Month()},
|
||||
MonthDays: utils.MonthDays{currentTime.Day()},
|
||||
WeekDays: utils.WeekDays{},
|
||||
StartTime: fmtTime,
|
||||
EndTime: "",
|
||||
}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func verifyFormat(tStr string) bool {
|
||||
if tStr == utils.EmptyString || tStr == utils.MetaASAP ||
|
||||
strings.HasPrefix(tStr, utils.PlusChar) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(tStr) > 8 { // hh:mm:ss
|
||||
return false
|
||||
}
|
||||
if a := strings.Split(tStr, utils.InInFieldSep); len(a) != 3 {
|
||||
return false
|
||||
} else {
|
||||
if _, err := strconv.Atoi(a[0]); err != nil {
|
||||
return false
|
||||
} else if _, err := strconv.Atoi(a[1]); err != nil {
|
||||
return false
|
||||
} else if _, err := strconv.Atoi(a[2]); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1807,7 +1807,8 @@ func (dm *DataManager) SetTiming(t *utils.TPTiming) (err error) {
|
||||
return utils.ErrNoDatabaseConn
|
||||
}
|
||||
// Check if time strings can be split in a time format before storing in db
|
||||
if t.StartTime != utils.EmptyString && t.StartTime != utils.MetaASAP && !utils.IsTimeFormated(t.StartTime) {
|
||||
if t.StartTime != utils.EmptyString && t.StartTime != utils.MetaASAP &&
|
||||
!strings.HasPrefix(t.StartTime, utils.PlusChar) && !utils.IsTimeFormated(t.StartTime) {
|
||||
return utils.ErrInvalidTime(t.StartTime)
|
||||
}
|
||||
if t.EndTime != utils.EmptyString && t.EndTime != utils.MetaASAP && !utils.IsTimeFormated(t.EndTime) {
|
||||
|
||||
@@ -106,15 +106,19 @@ func (s *Scheduler) Loop() {
|
||||
go a0.Execute(s.fltrS, utils.SchedulerS)
|
||||
// if after execute the next start time is in the past then
|
||||
// do not add it to the queue
|
||||
a0.ResetStartTimeCache()
|
||||
now = time.Now().Add(time.Second)
|
||||
start = a0.GetNextStartTime(now)
|
||||
if start.Before(now) {
|
||||
if strings.HasPrefix(a0.Timing.Timing.StartTime, utils.PlusChar) {
|
||||
s.queue = s.queue[1:]
|
||||
} else {
|
||||
s.queue = append(s.queue, a0)
|
||||
s.queue = s.queue[1:]
|
||||
sort.Sort(s.queue)
|
||||
a0.ResetStartTimeCache()
|
||||
now = time.Now().Add(time.Second)
|
||||
start = a0.GetNextStartTime(now)
|
||||
if start.Before(now) {
|
||||
s.queue = s.queue[1:]
|
||||
} else {
|
||||
s.queue = append(s.queue, a0)
|
||||
s.queue = s.queue[1:]
|
||||
sort.Sort(s.queue)
|
||||
}
|
||||
}
|
||||
s.Unlock()
|
||||
} else {
|
||||
|
||||
@@ -367,8 +367,14 @@ func (t *TPTiming) IsActiveAt(tm time.Time) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true if string can be split in 3 sperated by ":" signs. "00:00:00"
|
||||
// Returns true if string can be split in 3 sperated by ":" signs. "00:00:00", or if
|
||||
// its parsable to a duration
|
||||
func IsTimeFormated(t string) bool {
|
||||
if strings.HasPrefix(t, PlusChar) {
|
||||
if _, err := time.ParseDuration(strings.TrimPrefix(t, PlusChar)); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(strings.Split(t, ":")) == 3
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@ const (
|
||||
FilterValStart = "("
|
||||
FilterValEnd = ")"
|
||||
PlusChar = "+"
|
||||
MinusChar = "-"
|
||||
JSON = "json"
|
||||
JSONCaps = "JSON"
|
||||
GOBCaps = "GOB"
|
||||
@@ -1183,6 +1184,7 @@ const (
|
||||
MetaDynamicThreshold = "*dynamic_threshold"
|
||||
MetaDynamicStats = "*dynamic_stats"
|
||||
MetaDynamicAttribute = "*dynamic_attribute"
|
||||
MetaDynamicActionPlan = "*dynamic_action_plan"
|
||||
ActionID = "ActionID"
|
||||
ActionType = "ActionType"
|
||||
ActionValue = "ActionValue"
|
||||
|
||||
Reference in New Issue
Block a user