Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement daily schedule sp #28

Merged
merged 8 commits into from
Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions policy/subpolicy/daily_schedule_subpolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package subpolicy

import (
"fmt"

"github.com/datagovsg/nomad-parametric-autoscaler/resources"
"github.com/datagovsg/nomad-parametric-autoscaler/utils"
"github.com/mitchellh/mapstructure"
)

// DailyScheduleSubPolicy is a subpolicy that extends the `SubPolicy` interface
// and takes in the GenericSubpolicy struct
type DailyScheduleSubPolicy struct {
Name string
managedResources []resources.Resource
metadata DailyScheduleSubPolicyMetadata
}

type ScalingWindow struct {
Begin int `json:"Begin"`
End int `json:"End"`
Count int `json:"Count"`
}

func (sw ScalingWindow) IsInWindow(time int) bool {
return sw.Begin <= time && time < sw.End
}

// DailyScheduleSubPolicyMetadata represents metadata unique to DailyScheduleSubPolicy
// define windows of scaling through the day and a fallback default
type DailyScheduleSubPolicyMetadata struct {
Default *int `json:"Default"`
Schedule []ScalingWindow `json:"Schedule"`
}

func NewDailyScheduleSubPolicy(name string, mr []resources.Resource, meta interface{}) (*DailyScheduleSubPolicy, error) {
var decoded DailyScheduleSubPolicyMetadata
mapstructure.Decode(meta, &decoded)

if err := verifyDailyScheduleMetadata(decoded); err != nil {
return nil, err
}

return &DailyScheduleSubPolicy{
Name: name,
managedResources: mr,
metadata: decoded,
}, nil
}

func verifyDailyScheduleMetadata(meta DailyScheduleSubPolicyMetadata) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too bad mapstructure doesn't support the struct tag binding:"Required" like in Gin, otherwise this wouldnt be necessary

https://github.com/golang/go/wiki/Well-known-struct-tags

if meta.Default == nil {
return fmt.Errorf("Default missing from DailyScheduleSubPolicyMetadata")
}

if meta.Schedule == nil {
return fmt.Errorf("Schedule missing from DailyScheduleSubPolicyMetadata")
}

return nil
}

func (dssp DailyScheduleSubPolicy) GetManagedResources() []resources.Resource {
return dssp.managedResources
}

func (dssp *DailyScheduleSubPolicy) RecommendCount() map[resources.Resource]int {
currentTime := utils.GetCurrentTimeHHMM()

output := make(map[resources.Resource]int)

for _, resc := range dssp.managedResources {
var recommendation int
windowExist := false

// uses count of the first window that current time falls into
for _, sw := range dssp.metadata.Schedule {
if sw.IsInWindow(currentTime) {
recommendation = sw.Count
windowExist = true
break
}
}

if !windowExist {
recommendation = *dssp.metadata.Default
}

output[resc] = recommendation
}
return output
}

func (dssp *DailyScheduleSubPolicy) DeriveGenericSubpolicy() GenericSubPolicy {
resourceNameList := make([]string, 0)
for _, r := range dssp.managedResources {
resourceNameList = append(resourceNameList, r.ResourceName())
}

return GenericSubPolicy{
Name: dssp.Name,
ManagedResources: resourceNameList,
Metadata: dssp.metadata,
}
}
42 changes: 42 additions & 0 deletions policy/subpolicy/daily_schedule_subpolicy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package subpolicy

import (
"testing"

"github.com/datagovsg/nomad-parametric-autoscaler/resources"
)

func TestDecodingDailyScheduleSP(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: can include tests for invalid body should return error, valid body but invalid params should return error

input := map[string]interface{}{
"Default": 9.1,
"Schedule": []map[string]int{
{"Begin": 1, "End": 3, "Count": 3},
{"Begin": 3, "End": 4, "Count": 3},
},
}
emptyList := make([]resources.Resource, 0)
dssp, err := NewDailyScheduleSubPolicy("a", emptyList, input)
if err != nil {
t.Errorf("Expected to have no errors")
}

if *dssp.metadata.Default != 9 {
t.Errorf("Expected floats to be truncated")
}
}

func TestWindowChecking(t *testing.T) {
sw := ScalingWindow{
Begin: 0000,
End: 0200,
Count: 10,
}

if !sw.IsInWindow(0000) {
t.Errorf("Expected 000hrs to be within window")
}

if sw.IsInWindow(0200) {
t.Errorf("Expected 0200hrs to be out of window")
}
}
4 changes: 2 additions & 2 deletions policy/subpolicy/office_hour_subpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package subpolicy

import (
"fmt"
"time"

"github.com/datagovsg/nomad-parametric-autoscaler/resources"
"github.com/datagovsg/nomad-parametric-autoscaler/utils"
"github.com/mitchellh/mapstructure"
)

Expand Down Expand Up @@ -63,7 +63,7 @@ func (ohsp OfficeHourSubPolicy) GetManagedResources() []resources.Resource {
}

func (ohsp *OfficeHourSubPolicy) RecommendCount() map[resources.Resource]int {
currentHour := time.Now().UTC().Hour() + 8
currentHour := utils.GetCurrentTime().Hour()

output := make(map[resources.Resource]int)

Expand Down
6 changes: 6 additions & 0 deletions policy/subpolicy/subpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func CreateSpecificSubpolicy(gsp GenericSubPolicy, mr []resources.Resource) (Sub
return nil, err
}
return sp, nil
case types.DailySchedule:
sp, err := NewDailyScheduleSubPolicy(gsp.Name, mr, gsp.Metadata)
if err != nil {
return nil, err
}
return sp, nil
default:
return nil, fmt.Errorf("%v is not a valid subpolicy", gsp.Name)
}
Expand Down
3 changes: 2 additions & 1 deletion types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package types

const CoreRatio = "CoreRatio"
const OfficeHour = "OfficeHour"
const DailySchedule = "DailySchedule"

const ChangeTypeMultiply = "multiply"
const ChangeTypeUntil = "until"

const EnsembleConservative = "Conservative"
const EnsembleAverage = "Average"

var SubpolicyList = []string{CoreRatio, OfficeHour}
var SubpolicyList = []string{CoreRatio, OfficeHour, DailySchedule}
var EnsemblerList = []string{EnsembleConservative, EnsembleAverage}
10 changes: 10 additions & 0 deletions utils/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package utils

import "os"

func GetEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
18 changes: 18 additions & 0 deletions utils/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package utils

import (
"time"
)

func GetCurrentTime() time.Time {
loc, err := time.LoadLocation(GetEnv("TZ", "Asia/Singapore"))
if err != nil {
return time.Now().UTC().Add(8 * time.Hour)
}
return time.Now().In(loc)
}

func GetCurrentTimeHHMM() int {
now := GetCurrentTime()
return (now.Hour())*100 + now.Minute()
}