From f6ae317722755f4cc5c6ed3f8f9cb4263f0e167b Mon Sep 17 00:00:00 2001 From: Aleksandr Rybolovlev Date: Wed, 5 Jun 2024 19:37:22 +0200 Subject: [PATCH] Filter workspaces by current run status --- .../agentpool_controller_autoscaling.go | 153 ++++++------------ 1 file changed, 53 insertions(+), 100 deletions(-) diff --git a/controllers/agentpool_controller_autoscaling.go b/controllers/agentpool_controller_autoscaling.go index 9e06d210..5b471667 100644 --- a/controllers/agentpool_controller_autoscaling.go +++ b/controllers/agentpool_controller_autoscaling.go @@ -12,115 +12,71 @@ import ( tfc "github.com/hashicorp/go-tfe" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" appv1alpha2 "github.com/hashicorp/terraform-cloud-operator/api/v1alpha2" ) -func computeRequiredAgentsForWorkspace(ctx context.Context, ap *agentPoolInstance, workspaceID string) (int, error) { - statuses := []string{ +func computeRequiredAgents(ctx context.Context, ap *agentPoolInstance) (int32, error) { + required := 0 + runStatuses := []string{ string(tfc.RunPlanQueued), string(tfc.RunApplyQueued), string(tfc.RunApplying), string(tfc.RunPlanning), } - runs, err := ap.tfClient.Client.Runs.List(ctx, workspaceID, &tfc.RunListOptions{ - Status: strings.Join(statuses, ","), - }) - if err != nil { - return 0, err - } - return len(runs.Items), nil -} - -func getAllAgentPoolWorkspaceIDs(ctx context.Context, ap *agentPoolInstance) ([]string, error) { - agentPool, err := ap.tfClient.Client.AgentPools.Read(ctx, ap.instance.Status.AgentPoolID) - if err != nil { - return []string{}, nil - } - ids := []string{} - for _, w := range agentPool.Workspaces { - ids = append(ids, w.ID) - } - return ids, nil -} + workspaceNames := map[string]struct{}{} + workspaceIDs := map[string]struct{}{} -func getTargetWorkspaceIDs(ctx context.Context, ap *agentPoolInstance) ([]string, error) { - workspaces := ap.instance.Spec.AgentDeploymentAutoscaling.TargetWorkspaces - if workspaces == nil { - return getAllAgentPoolWorkspaceIDs(ctx, ap) - } - workspaceIDs := map[string]struct{}{} // NOTE: this is a map so we avoid duplicates when using wildcards - for _, w := range *workspaces { - if w.WildcardName != "" { - ids, err := getTargetWorkspaceIDsByWildcardName(ctx, ap, w) - if err != nil { - return []string{}, err - } - for _, id := range ids { - workspaceIDs[id] = struct{}{} - } - continue - } - id, err := getTargetWorkspaceID(ctx, ap, w) + nextPage := 1 + for nextPage > 0 { + workspaceList, err := ap.tfClient.Client.Workspaces.List(ctx, ap.instance.Spec.Organization, &tfc.WorkspaceListOptions{ + CurrentRunStatus: strings.Join(runStatuses, ","), + ListOptions: tfc.ListOptions{ + PageSize: 100, + PageNumber: nextPage, + }, + }) if err != nil { - return []string{}, err + return 0, err } - workspaceIDs[id] = struct{}{} - } - ids := []string{} - for v := range workspaceIDs { - ids = append(ids, v) - } - return ids, nil -} - -func getTargetWorkspaceID(ctx context.Context, ap *agentPoolInstance, targetWorkspace appv1alpha2.TargetWorkspace) (string, error) { - if targetWorkspace.ID != "" { - return targetWorkspace.ID, nil - } - list, err := ap.tfClient.Client.Workspaces.List(ctx, ap.instance.Spec.Organization, &tfc.WorkspaceListOptions{ - Search: targetWorkspace.Name, - }) - if err != nil { - return "", err - } - for _, w := range list.Items { - if w.Name == targetWorkspace.Name { - return w.ID, nil + nextPage = workspaceList.NextPage + for _, ws := range workspaceList.Items { + if ws.AgentPool.ID == ap.instance.Status.AgentPoolID { + workspaceNames[ws.Name] = struct{}{} + workspaceIDs[ws.ID] = struct{}{} + } } - } - return "", fmt.Errorf("no such workspace found %q", targetWorkspace.Name) -} -func getTargetWorkspaceIDsByWildcardName(ctx context.Context, ap *agentPoolInstance, targetWorkspace appv1alpha2.TargetWorkspace) ([]string, error) { - list, err := ap.tfClient.Client.Workspaces.List(ctx, ap.instance.Spec.Organization, &tfc.WorkspaceListOptions{ - WildcardName: targetWorkspace.WildcardName, - }) - if err != nil { - return []string{}, err - } - workspaceIDs := []string{} - for _, w := range list.Items { - workspaceIDs = append(workspaceIDs, w.ID) } - return workspaceIDs, nil -} -func computeRequiredAgents(ctx context.Context, ap *agentPoolInstance) (int32, error) { - required := 0 - workspaceIDs, err := getTargetWorkspaceIDs(ctx, ap) - if err != nil { - return 0, err + if ap.instance.Spec.AgentDeploymentAutoscaling.TargetWorkspaces == nil { + return int32(len(workspaceNames)), nil } - for _, workspaceID := range workspaceIDs { - r, err := computeRequiredAgentsForWorkspace(ctx, ap, workspaceID) - if err != nil { - return 0, err + + for _, t := range *ap.instance.Spec.AgentDeploymentAutoscaling.TargetWorkspaces { + switch { + case t.Name != "": + if _, ok := workspaceNames[t.Name]; ok { + required++ + } + // info message? + case t.ID != "": + if _, ok := workspaceIDs[t.ID]; ok { + required++ + } + case t.WildcardName != "": + nn := strings.Trim(t.WildcardName, "*") + for w := range workspaceNames { + if strings.Contains(w, nn) { + required++ + delete(workspaceNames, w) + } + } } - required += r } + return int32(required), nil } @@ -166,16 +122,12 @@ func (r *AgentPoolReconciler) reconcileAgentAutoscaling(ctx context.Context, ap ap.log.Info("Reconcile Agent Autoscaling", "msg", "new reconciliation event") - status := ap.instance.Status.AgentDeploymentAutoscalingStatus - if status != nil { - lastScalingEvent := status.LastScalingEvent - if lastScalingEvent != nil { - lastScalingEventSeconds := int(time.Since(lastScalingEvent.Time).Seconds()) - cooldownPeriodSeconds := ap.instance.Spec.AgentDeploymentAutoscaling.CooldownPeriodSeconds - if lastScalingEventSeconds <= int(*cooldownPeriodSeconds) { - ap.log.Info("Reconcile Agent Autoscaling", "msg", "autoscaler is within the cooldown period, skipping") - return nil - } + if s := ap.instance.Status.AgentDeploymentAutoscalingStatus; s != nil && s.LastScalingEvent != nil { + lastScalingEventSeconds := int(time.Since(s.LastScalingEvent.Time).Seconds()) + cooldownPeriodSeconds := int(*ap.instance.Spec.AgentDeploymentAutoscaling.CooldownPeriodSeconds) + if lastScalingEventSeconds <= cooldownPeriodSeconds { + ap.log.Info("Reconcile Agent Autoscaling", "msg", "autoscaler is within the cooldown period, skipping") + return nil } } @@ -185,6 +137,7 @@ func (r *AgentPoolReconciler) reconcileAgentAutoscaling(ctx context.Context, ap r.Recorder.Eventf(&ap.instance, corev1.EventTypeWarning, "AutoscaleAgentPoolDeployment", "Autoscaling failed: %v", err.Error()) return err } + ap.log.Info("Reconcile Agent Autoscaling", "msg", fmt.Sprintf("%d workspaces have pending runs", requiredAgents)) currentReplicas, err := r.getAgentDeploymentReplicas(ctx, ap) if err != nil { @@ -208,7 +161,7 @@ func (r *AgentPoolReconciler) reconcileAgentAutoscaling(ctx context.Context, ap } ap.instance.Status.AgentDeploymentAutoscalingStatus = &appv1alpha2.AgentDeploymentAutoscalingStatus{ DesiredReplicas: &desiredReplicas, - LastScalingEvent: &v1.Time{ + LastScalingEvent: &metav1.Time{ Time: time.Now(), }, }