Skip to content

Commit

Permalink
cooldownの基準として各リソースのModifiedAtを用いる (#475)
Browse files Browse the repository at this point in the history
* docs: cooldown - modified_atを持たないリソースについて追記
* cooldownの基準として各リソースのModifiedAtを用いる
  • Loading branch information
yamamoto-febc authored Feb 20, 2023
1 parent 037612c commit 1efc688
Show file tree
Hide file tree
Showing 19 changed files with 321 additions and 80 deletions.
18 changes: 13 additions & 5 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,6 @@ func (c *Core) currentJob(ctx *RequestContext) *JobStatus {

func (c *Core) handle(ctx *RequestContext) (*JobStatus, string, error) {
job := c.currentJob(ctx)
if !job.Acceptable(ctx.request.requestType) {
ctx.Logger().Info("status", request.ScalingJobStatus_JOB_IGNORED, "message", "job is in an unacceptable state") //nolint
return job, "job is in an unacceptable state", nil
}

if c.stopping {
ctx.Logger().Info("status", request.ScalingJobStatus_JOB_IGNORED, "message", "core is shutting down") //nolint
return job, "core is shutting down", nil
Expand All @@ -214,6 +209,19 @@ func (c *Core) handle(ctx *RequestContext) (*JobStatus, string, error) {
return job, "", err
}

// さくらのクラウドAPI経由で対象リソース情報を参照し最終更新日時を取得
lastModifiedAt, err := rds.LastModifiedAt(ctx, c.config.APIClient())
if err != nil {
job.SetStatus(request.ScalingJobStatus_JOB_CANCELED) // まだ実行前のためCANCELEDを返す
ctx.Logger().Info("status", request.ScalingJobStatus_JOB_CANCELED, "error", err) //nolint
return job, "", err
}

if !job.Acceptable(ctx.request.requestType, lastModifiedAt) {
ctx.Logger().Info("status", request.ScalingJobStatus_JOB_IGNORED, "message", "job is in an unacceptable state") //nolint
return job, "job is in an unacceptable state", nil
}

job.SetStatus(request.ScalingJobStatus_JOB_ACCEPTED)
ctx.Logger().Info("status", request.ScalingJobStatus_JOB_ACCEPTED) //nolint

Expand Down
27 changes: 12 additions & 15 deletions core/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,20 @@ import (
//
// Inputsからのリクエストパラメータ ResourceNameごとに作成される
type JobStatus struct {
id string
status request.ScalingJobStatus
statusChanged time.Time
coolDown *CoolDown
mu sync.Mutex
id string
status request.ScalingJobStatus
coolDown *CoolDown
mu sync.Mutex
}

func NewJobStatus(req *requestInfo, coolDown *CoolDown) *JobStatus {
if coolDown == nil {
coolDown = &CoolDown{}
}
return &JobStatus{
id: req.ID(),
status: request.ScalingJobStatus_JOB_UNKNOWN,
statusChanged: time.Now(),
coolDown: coolDown,
id: req.ID(),
status: request.ScalingJobStatus_JOB_DONE, // 完了状態 == ジョブ受け入れ可能ということで初期値にしておく
coolDown: coolDown,
}
}

Expand All @@ -61,28 +59,27 @@ func (j *JobStatus) SetStatus(status request.ScalingJobStatus) {
defer j.mu.Unlock()

j.status = status
j.statusChanged = time.Now()
}

func (j *JobStatus) String() string {
return fmt.Sprintf("ID: %s Status: %s StatusChanged: %s", j.ID(), j.Status(), j.statusChanged)
return fmt.Sprintf("ID: %s Status: %s", j.ID(), j.Status())
}

// Acceptable このジョブが新規に受け入れ可能(新たに起動できる)状態の場合true
func (j *JobStatus) Acceptable(requestType RequestTypes) bool {
func (j *JobStatus) Acceptable(requestType RequestTypes, lastModifiedAt time.Time) bool {
switch j.Status() {
case request.ScalingJobStatus_JOB_ACCEPTED, request.ScalingJobStatus_JOB_RUNNING:
// すでに受け入れ済み or 実行中
return false
default:
// 以外は冷却期間でなければtrue
return !j.inCoolDownTime(requestType)
return !j.inCoolDownTime(requestType, lastModifiedAt)
}
}

// inCoolDownTime StatusがDONE、かつ冷却期間内であればtrue
func (j *JobStatus) inCoolDownTime(requestType RequestTypes) bool {
func (j *JobStatus) inCoolDownTime(requestType RequestTypes, lastModifiedAt time.Time) bool {
coolDownTime := j.coolDown.Duration(requestType)
return j.Status() == request.ScalingJobStatus_JOB_DONE &&
j.statusChanged.After(time.Now().Add(-1*coolDownTime))
lastModifiedAt.After(time.Now().Add(-1*coolDownTime))
}
101 changes: 50 additions & 51 deletions core/job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,155 +23,154 @@ import (

func TestJobStatus_Acceptable(t *testing.T) {
type fields struct {
status request.ScalingJobStatus
statusChanged time.Time
coolDown *CoolDown
status request.ScalingJobStatus
coolDown *CoolDown
}
tests := []struct {
name string
fields fields
requestType RequestTypes
want bool
name string
fields fields
requestType RequestTypes
lastModifiedAt time.Time
want bool
}{
{
name: "returns true if status is DONE and is not in cooling down time: up",
fields: fields{
status: request.ScalingJobStatus_JOB_DONE,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_DONE,
coolDown: &CoolDown{
Up: 1,
Down: 1000,
},
},
requestType: requestTypeUp,
want: true,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: true,
},
{
name: "returns false if is in cooling down time: up",
fields: fields{
status: request.ScalingJobStatus_JOB_DONE,
statusChanged: time.Now(),
status: request.ScalingJobStatus_JOB_DONE,
coolDown: &CoolDown{
Up: 1000,
Down: 1,
},
},
requestType: requestTypeUp,
want: false,
lastModifiedAt: time.Now(),
requestType: requestTypeUp,
want: false,
},
{
name: "returns true if status is DONE and is not in cooling down time: down",
fields: fields{
status: request.ScalingJobStatus_JOB_DONE,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_DONE,
coolDown: &CoolDown{
Up: 1000,
Down: 1,
},
},
requestType: requestTypeDown,
want: true,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeDown,
want: true,
},
{
name: "returns false if is in cooling down time: down",
fields: fields{
status: request.ScalingJobStatus_JOB_DONE,
statusChanged: time.Now(),
status: request.ScalingJobStatus_JOB_DONE,
coolDown: &CoolDown{
Up: 1,
Down: 1000,
},
},
requestType: requestTypeDown,
want: false,
lastModifiedAt: time.Now(),
requestType: requestTypeDown,
want: false,
},
{
name: "returns false if status is RUNNING",
fields: fields{
status: request.ScalingJobStatus_JOB_RUNNING,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_RUNNING,
coolDown: &CoolDown{
Up: 1,
Down: 1,
},
},
requestType: requestTypeUp,
want: false,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: false,
},
{
name: "returns true if status is UNKNOWN",
fields: fields{
status: request.ScalingJobStatus_JOB_UNKNOWN,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_UNKNOWN,
coolDown: &CoolDown{
Up: 1,
Down: 1,
},
},
requestType: requestTypeUp,
want: true,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: true,
},
{
name: "returns true if status is CANCELED",
fields: fields{
status: request.ScalingJobStatus_JOB_CANCELED,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_CANCELED,
coolDown: &CoolDown{
Up: 1,
Down: 1,
},
},
requestType: requestTypeUp,
want: true,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: true,
},
{
name: "returns true if status is DONE_NOOP",
fields: fields{
status: request.ScalingJobStatus_JOB_DONE_NOOP,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_DONE_NOOP,
coolDown: &CoolDown{
Up: 1,
Down: 1,
},
},
requestType: requestTypeUp,
want: true,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: true,
},
{
name: "returns false if status is ACCEPTED",
fields: fields{
status: request.ScalingJobStatus_JOB_ACCEPTED,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_ACCEPTED,
coolDown: &CoolDown{
Up: 1,
Down: 1,
},
},
requestType: requestTypeUp,
want: false,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: false,
},
{
name: "returns true if status is FAILED",
fields: fields{
status: request.ScalingJobStatus_JOB_FAILED,
statusChanged: time.Now().Add(-2 * time.Second),
status: request.ScalingJobStatus_JOB_FAILED,
coolDown: &CoolDown{
Up: 1,
Down: 1,
},
},
requestType: requestTypeUp,
want: true,
lastModifiedAt: time.Now().Add(-2 * time.Second),
requestType: requestTypeUp,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
j := &JobStatus{
status: tt.fields.status,
statusChanged: tt.fields.statusChanged,
coolDown: tt.fields.coolDown,
status: tt.fields.status,
coolDown: tt.fields.coolDown,
}
if got := j.Acceptable(tt.requestType); got != tt.want {
if got := j.Acceptable(tt.requestType, tt.lastModifiedAt); got != tt.want {
t.Errorf("Acceptable() = %v, want %v", got, tt.want)
}
})
Expand Down
16 changes: 16 additions & 0 deletions core/resource_def_elb.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package core
import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-multierror"
"github.com/sacloud/autoscaler/validate"
Expand Down Expand Up @@ -134,3 +135,18 @@ func (d *ResourceDefELB) findCloudResources(ctx context.Context, apiClient iaas.
}
return found.ProxyLBs, nil
}

// LastModifiedAt この定義が対象とするリソース(群)の最終更新日時を返す
func (d *ResourceDefELB) LastModifiedAt(ctx *RequestContext, apiClient iaas.APICaller) (time.Time, error) {
cloudResources, err := d.findCloudResources(ctx, apiClient)
if err != nil {
return time.Time{}, err
}
last := time.Time{}
for _, r := range cloudResources {
if r.GetModifiedAt().After(last) {
last = r.GetModifiedAt()
}
}
return last, nil
}
19 changes: 18 additions & 1 deletion core/resource_def_parent.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/goccy/go-yaml"
"github.com/hashicorp/go-multierror"
Expand Down Expand Up @@ -92,9 +93,25 @@ func (d *ParentResourceDef) Compute(ctx *RequestContext, apiClient iaas.APICalle
return resources, nil
}

// LastModifiedAt この定義が対象とするリソース(群)の最終更新日時を返す
func (d *ParentResourceDef) LastModifiedAt(ctx *RequestContext, apiClient iaas.APICaller) (time.Time, error) {
cloudResources, err := d.findCloudResources(ctx, apiClient, ctx.zone)
if err != nil {
return time.Time{}, err
}
last := time.Time{}
for _, r := range cloudResources {
if r.GetModifiedAt().After(last) {
last = r.GetModifiedAt()
}
}
return last, nil
}

type SakuraCloudResource interface {
GetID() types.ID
GetName() string
GetModifiedAt() time.Time
}

func (d *ParentResourceDef) findCloudResources(ctx context.Context, apiClient iaas.APICaller, zone string) ([]SakuraCloudResource, error) {
Expand Down Expand Up @@ -136,7 +153,7 @@ func (d *ParentResourceDef) findCloudResources(ctx context.Context, apiClient ia
return nil, fmt.Errorf("computing status failed: %s", err)
}
for _, v := range found.Internet {
results = append(results, v)
results = append(results, &sakuraCloudRouter{Internet: v, zone: zone})
}
case ResourceTypeLoadBalancer:
op := iaas.NewLoadBalancerOp(apiClient)
Expand Down
Loading

0 comments on commit 1efc688

Please sign in to comment.