From 149acf31f35c5716a9a80dd53638e73385b98a01 Mon Sep 17 00:00:00 2001 From: nightfury1204 Date: Wed, 31 Jul 2024 21:34:46 +0100 Subject: [PATCH 1/2] Fix autoscale desired count --- provider/aws/formation/rack.json | 1 + provider/aws/formation/service.json.tmpl | 20 ++- .../aws/lambda/formation/handler/formation.go | 4 + provider/aws/lambda/formation/handler/math.go | 135 ++++++++++++++++++ provider/aws/releases.go | 25 +--- provider/aws/service.go | 18 --- 6 files changed, 159 insertions(+), 44 deletions(-) create mode 100644 provider/aws/lambda/formation/handler/math.go diff --git a/provider/aws/formation/rack.json b/provider/aws/formation/rack.json index 9e85aeaf2b..b15fcd20ab 100644 --- a/provider/aws/formation/rack.json +++ b/provider/aws/formation/rack.json @@ -296,6 +296,7 @@ "Value": { "Ref": "Cluster" } }, "CustomTopic": { + "Export": { "Name": { "Fn::Sub": "${AWS::StackName}:CustomTopic" } }, "Value": { "Fn::GetAtt": [ "CustomTopic", "Arn" ] } }, "CustomerManagedKey": { diff --git a/provider/aws/formation/service.json.tmpl b/provider/aws/formation/service.json.tmpl index d6f11b7ece..d2ee0d0d32 100644 --- a/provider/aws/formation/service.json.tmpl +++ b/provider/aws/formation/service.json.tmpl @@ -212,6 +212,22 @@ } }, "Resources": { + "MinCount": { + "Type": "Custom::MathMin", + "Properties": { + "ServiceToken": { "Fn::ImportValue": { "Fn::Sub": "${Rack}:CustomTopic" } }, + "X": "{{.Scale.Count.Min}}", + "Y": { "Ref": "Count" } + } + }, + "MaxCount": { + "Type": "Custom::MathMax", + "Properties": { + "ServiceToken": { "Fn::ImportValue": { "Fn::Sub": "${Rack}:CustomTopic" } }, + "X": "{{.Scale.Count.Max}}", + "Y": { "Ref": "Count" } + } + }, "AutoscalingRole": { "Type": "AWS::IAM::Role", "Properties": { @@ -512,8 +528,8 @@ "AutoscalingTarget": { "Type": "AWS::ApplicationAutoScaling::ScalableTarget", "Properties": { - "MaxCapacity": "{{.Scale.Count.Max}}", - "MinCapacity": "{{.Scale.Count.Min}}", + "MaxCapacity": { "Fn::GetAtt": [ "MaxCount", "Value" ] }, + "MinCapacity": { "Fn::GetAtt": [ "MinCount", "Value" ] }, "ResourceId": { "Fn::Sub": [ "service/${Cluster}/${Service.Name}", { "Cluster": { "Fn::ImportValue": { "Fn::Sub": "${Rack}:Cluster" } } } ] }, "RoleARN": { "Fn::GetAtt": [ "AutoscalingRole", "Arn" ] }, "ScalableDimension": "ecs:service:DesiredCount", diff --git a/provider/aws/lambda/formation/handler/formation.go b/provider/aws/lambda/formation/handler/formation.go index 81edf38067..56d6d04d26 100644 --- a/provider/aws/lambda/formation/handler/formation.go +++ b/provider/aws/lambda/formation/handler/formation.go @@ -182,6 +182,10 @@ func HandleRequest(freq Request) error { physical, outputs, err = HandleSGIngress(freq) case "Custom::SNSSubscription": physical, outputs, err = HandleSNSSubcription(freq) + case "Custom::MathMax": + physical, outputs, err = HandleMathMax(freq) + case "Custom::MathMin": + physical, outputs, err = HandleMathMin(freq) default: physical = "" err = fmt.Errorf("unknown ResourceType: %s", freq.ResourceType) diff --git a/provider/aws/lambda/formation/handler/math.go b/provider/aws/lambda/formation/handler/math.go new file mode 100644 index 0000000000..8f32efd13a --- /dev/null +++ b/provider/aws/lambda/formation/handler/math.go @@ -0,0 +1,135 @@ +package handler + +import ( + "fmt" + "strconv" + "time" +) + +func HandleMathMax(req Request) (string, map[string]string, error) { + defer recoverFailure(req) + + switch req.RequestType { + case "Create": + fmt.Println("CREATING Max") + fmt.Printf("req %+v\n", req) + return CreateMathMax(req) + case "Update": + fmt.Println("UPDATING Max") + fmt.Printf("req %+v\n", req) + return UpdateMathMax(req) + case "Delete": + fmt.Println("no need to delete") + fmt.Printf("req %+v\n", req) + return req.PhysicalResourceId, nil, nil + } + + return "", nil, fmt.Errorf("unknown RequestType: %s", req.RequestType) +} + +func HandleMathMin(req Request) (string, map[string]string, error) { + defer recoverFailure(req) + + switch req.RequestType { + case "Create": + fmt.Println("CREATING Min") + fmt.Printf("req %+v\n", req) + return CreateMathMin(req) + case "Update": + fmt.Println("UPDATING Min") + fmt.Printf("req %+v\n", req) + return UpdateMathMin(req) + case "Delete": + fmt.Println("no need to delete") + fmt.Printf("req %+v\n", req) + return req.PhysicalResourceId, nil, nil + } + + return "", nil, fmt.Errorf("unknown RequestType: %s", req.RequestType) +} + +func CreateMathMax(req Request) (string, map[string]string, error) { + val, err := mathMax(req) + if err != nil { + return "invalid", nil, err + } + + return fmt.Sprintf("mathmax-%d", time.Now().UnixNano()), map[string]string{ + "Value": val, + }, nil +} + +func UpdateMathMax(req Request) (string, map[string]string, error) { + val, err := mathMax(req) + if err != nil { + return "invalid", nil, err + } + + return req.PhysicalResourceId, map[string]string{ + "Value": val, + }, nil +} + +func CreateMathMin(req Request) (string, map[string]string, error) { + val, err := mathMin(req) + if err != nil { + return "invalid", nil, err + } + + return fmt.Sprintf("mathmin-%d", time.Now().UnixNano()), map[string]string{ + "Value": val, + }, nil +} + +func UpdateMathMin(req Request) (string, map[string]string, error) { + val, err := mathMin(req) + if err != nil { + return "invalid", nil, err + } + + return req.PhysicalResourceId, map[string]string{ + "Value": val, + }, nil +} + +func mathMax(req Request) (string, error) { + x, y, err := parseXY(req) + if err != nil { + return "", err + } + + if y > x { + x = y + } + + return strconv.Itoa(x), nil +} + +func mathMin(req Request) (string, error) { + x, y, err := parseXY(req) + if err != nil { + return "", err + } + + if y < x { + x = y + } + + return strconv.Itoa(x), nil +} + +func parseXY(req Request) (int, int, error) { + xStr := req.ResourceProperties["X"].(string) + yStr := req.ResourceProperties["Y"].(string) + + x, err := strconv.Atoi(xStr) + if err != nil { + return 0, 0, err + } + + y, err := strconv.Atoi(yStr) + if err != nil { + return 0, 0, err + } + return x, y, nil +} diff --git a/provider/aws/releases.go b/provider/aws/releases.go index 357f64b50b..b2c85bc102 100644 --- a/provider/aws/releases.go +++ b/provider/aws/releases.go @@ -14,7 +14,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/eventbridge" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/s3" @@ -340,7 +339,7 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt tp[fmt.Sprintf("ResourceTemplate%s", upperName(r.Name))] = ou.Url } - for i, s := range m.Services { + for _, s := range m.Services { min := s.Deployment.Minimum max := s.Deployment.Maximum @@ -386,27 +385,6 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt } tp[fmt.Sprintf("ServiceTemplate%s", upperName(s.Name))] = ou.Url - - sarn, err := p.serviceArn(r.App, s.Name) - if err != nil { - return err - } - - if sarn != "" { - res, err := p.describeServices(&ecs.DescribeServicesInput{ - Cluster: aws.String(p.Cluster), - Services: []*string{aws.String(sarn)}, - }) - if err != nil { - return err - } - - // when autoscale is on set m.Services[i].Scale.Count.Min to desired count - // since this is used to service count param from app - if autoscale && len(res.Services) == 1 && res.Services[0].DesiredCount != nil { - m.Services[i].Scale.Count.Min = int(*res.Services[0].DesiredCount) - } - } } for _, t := range m.Timers { @@ -440,7 +418,6 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt tp[fmt.Sprintf("TimerTemplate%s", upperName(t.Name))] = ou.Url } - // m.Services[i].Scale.Count.Min is mutated if service autoscaling is used data, err := formationTemplate("app", tp) if err != nil { return err diff --git a/provider/aws/service.go b/provider/aws/service.go index d1ca4510fc..3cdafd4903 100644 --- a/provider/aws/service.go +++ b/provider/aws/service.go @@ -260,23 +260,5 @@ func (p *Provider) ServiceUpdate(app, name string, opts structs.ServiceUpdateOpt return err } - if opts.Count != nil { - sarn, err := p.serviceArn(app, name) - if err != nil { - return err - } - - if sarn != "" { - _, err := p.ecs().UpdateService(&ecs.UpdateServiceInput{ - Cluster: aws.String(p.Cluster), - Service: aws.String(sarn), - DesiredCount: aws.Int64(int64(*opts.Count)), - }) - if err != nil { - return err - } - } - } - return nil } From 1a027cef338feb1756afecfae1627ce4cd54f397 Mon Sep 17 00:00:00 2001 From: nightfury1204 Date: Fri, 2 Aug 2024 17:28:09 +0100 Subject: [PATCH 2/2] Fix agent deployment due to autoscaling --- provider/aws/formation/service.json.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/aws/formation/service.json.tmpl b/provider/aws/formation/service.json.tmpl index d2ee0d0d32..6cc4a8fa5c 100644 --- a/provider/aws/formation/service.json.tmpl +++ b/provider/aws/formation/service.json.tmpl @@ -524,7 +524,7 @@ } }, {{ end }} - {{ if $.Autoscale }} + {{ if and ($.Autoscale) (not .Agent.Enabled) }} "AutoscalingTarget": { "Type": "AWS::ApplicationAutoScaling::ScalableTarget", "Properties": {