diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 6099cc0a6..18a50a830 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -3745,6 +3745,7 @@ type TemporalWorkerDeploymentCreateVersionCommand struct { GcpCloudRunMaxInstances int GcpCloudRunInitialInstances int GcpCloudRunUtilizationTarget float32 + GcpCloudRunScaleDownWaitTime cliext.FlagDuration } func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, parent *TemporalWorkerDeploymentCommand) *TemporalWorkerDeploymentCreateVersionCommand { @@ -3754,9 +3755,9 @@ func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, paren s.Command.Use = "create-version [flags]" s.Command.Short = "Create a new Worker Deployment Version" if hasHighlighting { - s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n\x1b[1mtemporal worker deployment create-version [options]\x1b[0m\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\x1b[0m\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n\x1b[1mtemporal worker deployment create-version [options]\x1b[0m\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\x1b[0m\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-wait-time 5m\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." } else { - s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n```\ntemporal worker deployment create-version [options]\n```\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\n```\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\n```\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n```\ntemporal worker deployment create-version [options]\n```\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\n```\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-wait-time 5m\n```\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.AwsLambdaFunctionArn, "aws-lambda-function-arn", "", "Qualified (contains version suffix) or unqualified AWS Lambda function ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment.") @@ -3767,10 +3768,12 @@ func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, paren s.Command.Flags().StringVar(&s.GcpCloudRunRegion, "gcp-cloud-run-region", "", "Region of the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().StringVar(&s.GcpCloudRunWorkerPool, "gcp-cloud-run-worker-pool", "", "GCP Cloud Run worker pool name to scale when there are no active pollers for task queue targets in the Worker Deployment.") s.Command.Flags().StringVar(&s.GcpCloudRunServiceAccount, "gcp-cloud-run-service-account", "", "Customer GCP service account the Temporal server impersonates to manage the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") - s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Defaults to 30 when unset. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together, and this value must be between the min and max (inclusive). Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Lower values keep more spare capacity per worker. Defaults to 0.8 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. Defaults to 30 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together, and this value must be between the min and max (inclusive). Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. Lower values keep more spare capacity per worker. Defaults to 0.8 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.GcpCloudRunScaleDownWaitTime = 0 + s.Command.Flags().Var(&s.GcpCloudRunScaleDownWaitTime, "gcp-cloud-run-scale-down-wait-time", "Duration the scaler waits after it last saw unmet task demand before it may scale the Cloud Run worker pool down. Raise this to keep the pool from scaling down before long-running or bursty activities finish. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. A value of 0s disables the wait. Defaults to 90s when unset. Only valid with --gcp-cloud-run-worker-pool.") s.DeploymentVersionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { @@ -4086,6 +4089,7 @@ type TemporalWorkerDeploymentUpdateVersionComputeConfigCommand struct { GcpCloudRunMaxInstances int GcpCloudRunInitialInstances int GcpCloudRunUtilizationTarget float32 + GcpCloudRunScaleDownWaitTime cliext.FlagDuration Remove bool } @@ -4096,9 +4100,9 @@ func NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx *CommandC s.Command.Use = "update-version-compute-config [flags]" s.Command.Short = "Update compute configuration for a Version" if hasHighlighting { - s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\x1b[0m\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\x1b[0m\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\x1b[0m\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the four scaler flags without the provider\nfields (all four must be set together):\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\x1b[0m\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\x1b[0m\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\x1b[0m\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-wait-time 5m\x1b[0m\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the five scaler flags without the provider\nfields (all five must be set together):\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-wait-time 5m\x1b[0m\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." } else { - s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\n```\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\n```\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\n```\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the four scaler flags without the provider\nfields (all four must be set together):\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\n```\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\n```\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\n```\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\n```\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-wait-time 5m\n```\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the five scaler flags without the provider\nfields (all five must be set together):\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-wait-time 5m\n```\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\n```\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.AwsLambdaFunctionArn, "aws-lambda-function-arn", "", "Qualified (contains version suffix) or unqualified AWS Lambda function ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment.") @@ -4109,10 +4113,12 @@ func NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx *CommandC s.Command.Flags().StringVar(&s.GcpCloudRunRegion, "gcp-cloud-run-region", "", "Region of the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().StringVar(&s.GcpCloudRunWorkerPool, "gcp-cloud-run-worker-pool", "", "GCP Cloud Run worker pool name to scale when there are no active pollers for task queue targets in the Worker Deployment.") s.Command.Flags().StringVar(&s.GcpCloudRunServiceAccount, "gcp-cloud-run-service-account", "", "Customer GCP service account the Temporal server impersonates to manage the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") - s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together, and this value must be between the min and max (inclusive). If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Lower values keep more spare capacity per worker. If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together, and this value must be between the min and max (inclusive). If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. Lower values keep more spare capacity per worker. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.GcpCloudRunScaleDownWaitTime = 0 + s.Command.Flags().Var(&s.GcpCloudRunScaleDownWaitTime, "gcp-cloud-run-scale-down-wait-time", "Duration the scaler waits after it last saw unmet task demand before it may scale the Cloud Run worker pool down. Raise this to keep the pool from scaling down before long-running or bursty activities finish. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must all be set together. A value of 0s disables the wait. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") s.Command.Flags().BoolVar(&s.Remove, "remove", false, "Removes any compute configuration associated with this Worker Deployment Version.") s.DeploymentVersionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index 593f00e39..09c8b8927 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -136,11 +136,12 @@ type formattedComputeConfigProvider struct { } type formattedComputeConfigScaler struct { - Type string `json:"type"` - MinInstances *int64 `json:"minInstances,omitempty"` - MaxInstances *int64 `json:"maxInstances,omitempty"` - InitialInstances *int64 `json:"initialInstances,omitempty"` - UtilizationTarget *float64 `json:"utilizationTarget,omitempty"` + Type string `json:"type"` + MinInstances *int64 `json:"minInstances,omitempty"` + MaxInstances *int64 `json:"maxInstances,omitempty"` + InitialInstances *int64 `json:"initialInstances,omitempty"` + UtilizationTarget *float64 `json:"utilizationTarget,omitempty"` + ScaleDownWaitTimeMs *int64 `json:"scaleDownWaitTimeMs,omitempty"` } func drainageStatusToStr(drainage client.WorkerDeploymentVersionDrainageStatus) (string, error) { @@ -393,6 +394,7 @@ const ( scalerKeyMaxCount = "max_count" scalerKeyInitialCount = "initial_count" scalerKeyUtilizationTarget = "utilization_target" + scalerKeyNoSyncQuietMs = "no_sync_quiet_ms" ) // scalerCountFromMap reads an integer worker-count value from a decoded scaler @@ -427,10 +429,11 @@ func scalerFloatFromMap(m map[string]any, key string) (float64, bool) { // scalerSettings holds the rate-based scaler settings surfaced for display. Each // field is nil when the corresponding key is absent from the scaler details. type scalerSettings struct { - minInstances *int64 - maxInstances *int64 - initialInstances *int64 - utilizationTarget *float64 + minInstances *int64 + maxInstances *int64 + initialInstances *int64 + utilizationTarget *float64 + scaleDownWaitTimeMs *int64 } // decodeScalerSettings extracts the rate-based scaler settings from a @@ -459,6 +462,9 @@ func decodeScalerSettings(s *computepb.ComputeScaler) scalerSettings { if v, ok := scalerFloatFromMap(m, scalerKeyUtilizationTarget); ok { out.utilizationTarget = &v } + if v, ok := scalerCountFromMap(m, scalerKeyNoSyncQuietMs); ok { + out.scaleDownWaitTimeMs = &v + } return out } @@ -490,6 +496,7 @@ func formatComputeConfigProto(cc *computepb.ComputeConfig) *formattedComputeConf fs.MaxInstances = set.maxInstances fs.InitialInstances = set.initialInstances fs.UtilizationTarget = set.utilizationTarget + fs.ScaleDownWaitTimeMs = set.scaleDownWaitTimeMs sg.Scaler = fs } sgs[name] = sg @@ -540,7 +547,8 @@ func computeConfigSummaryStr(cc *computepb.ComputeConfig) string { } summary := p.GetType() // Append whichever scaler settings are present so the one-line summary - // reflects the configured limits (ordered min, initial, max, utilization). + // reflects the configured limits (ordered min, initial, max, utilization, + // scale-down-wait-time-ms). set := decodeScalerSettings(sg.GetScaler()) parts := []string{} if set.minInstances != nil { @@ -555,6 +563,9 @@ func computeConfigSummaryStr(cc *computepb.ComputeConfig) string { if set.utilizationTarget != nil { parts = append(parts, fmt.Sprintf("utilization %g", *set.utilizationTarget)) } + if set.scaleDownWaitTimeMs != nil { + parts = append(parts, fmt.Sprintf("scale-down-wait-time-ms %d", *set.scaleDownWaitTimeMs)) + } if len(parts) > 0 { summary = fmt.Sprintf("%s (%s)", summary, strings.Join(parts, ", ")) } @@ -1134,50 +1145,41 @@ func scalerTypeForProvider(providerType string) (string, error) { return "", fmt.Errorf("no scaler mapping for compute provider %q", providerType) } -// gcpCloudRunScalerDetails builds the ComputeScaler.Details payload from the GCP -// Cloud Run scaling flags. It carries two independent groups of rate-based scaler -// settings: -// - the instance-count group (min_count/max_count/initial_count), which is -// all-or-none and must satisfy min <= initial <= max, and -// - utilization_target, a standalone fraction in (0, 1]. -// -// The *Set booleans come from cobra's Flags().Changed, so an omitted flag stays -// distinct from an explicit 0. Returns a nil payload when nothing is set, leaving -// WCI's defaults (min 0, max 30, initial 0, utilization_target 0.8) in effect. -// Every setting is GCP Cloud Run only; any use with another provider is rejected. -// Config keys mirror the WCI rate-based scaler: -// https://github.com/temporalio/temporal-auto-scaled-workers/blob/main/wci/workflow/scaling_algorithm/rate_based.go // gcpScalerFlags holds the GCP Cloud Run scaling flag values together with // whether each was actually set (from cobra's Flags().Changed). Pairing each // value with its Set bool keeps an omitted flag distinct from an explicit 0 and // removes the positional-argument risk of passing the raw values around. type gcpScalerFlags struct { - min int - minSet bool - max int - maxSet bool - initial int - initialSet bool - utilization float32 - utilizationSet bool + min int + minSet bool + max int + maxSet bool + initial int + initialSet bool + utilization float32 + utilizationSet bool + scaleDownWait time.Duration + scaleDownWaitSet bool } func (f gcpScalerFlags) anySet() bool { - return f.minSet || f.maxSet || f.initialSet || f.utilizationSet + return f.minSet || f.maxSet || f.initialSet || f.utilizationSet || f.scaleDownWaitSet } func (f gcpScalerFlags) allSet() bool { - return f.minSet && f.maxSet && f.initialSet && f.utilizationSet + return f.minSet && f.maxSet && f.initialSet && f.utilizationSet && f.scaleDownWaitSet } // gcpCloudRunScalerDetails builds the ComputeScaler.Details payload from the GCP -// Cloud Run scaling flags (min/max/initial instance counts and utilization -// target). The four form a single all-or-none group: setting any one requires -// all four. That keeps the min<=initial<=max relationship self-contained and -// avoids comparing an explicit value against WCI's default for an unset sibling. -// Returns a nil payload when nothing is set, leaving WCI's defaults (min 0, -// max 30, initial 0, utilization_target 0.8) in effect. Every setting is GCP -// Cloud Run only; any use with another provider is rejected. +// Cloud Run scaling flags (min/max/initial instance counts, utilization target, +// and the scale-down wait time). The five form a single all-or-none group: +// setting any one requires all five. That keeps the min<=initial<=max +// relationship self-contained, always writes the full scaler config so an update +// never leaves a stale sibling behind, and avoids comparing an explicit value +// against WCI's default for an unset sibling. Returns a nil payload when nothing +// is set, leaving WCI's defaults (min 0, max 30, initial 0, utilization_target +// 0.8, no_sync_quiet_ms 90000) in effect. Every setting is GCP Cloud Run only; +// any use with another provider is rejected. func gcpCloudRunScalerDetails(providerType string, f gcpScalerFlags) (*commonpb.Payload, error) { if !f.anySet() { return nil, nil @@ -1188,7 +1190,7 @@ func gcpCloudRunScalerDetails(providerType string, f gcpScalerFlags) (*commonpb. return nil, fmt.Errorf("the Cloud Run scaling flags are only valid with --gcp-cloud-run-worker-pool") } if !f.allSet() { - return nil, fmt.Errorf("--gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must be set together") + return nil, fmt.Errorf("--gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-wait-time must be set together") } if f.min < 0 { return nil, fmt.Errorf("--gcp-cloud-run-min-instances cannot be negative") @@ -1207,11 +1209,21 @@ func gcpCloudRunScalerDetails(providerType string, f gcpScalerFlags) (*commonpb. if f.utilization <= 0 || f.utilization > 1 { return nil, fmt.Errorf("--gcp-cloud-run-utilization-target must be greater than 0 and at most 1") } + // Validate the raw duration before converting: Milliseconds() truncates + // toward zero, which would hide a negative sub-millisecond value (turning it + // into 0, disabling the wait) or silently round a fractional millisecond. + if f.scaleDownWait < 0 { + return nil, fmt.Errorf("--gcp-cloud-run-scale-down-wait-time cannot be negative") + } + if f.scaleDownWait%time.Millisecond != 0 { + return nil, fmt.Errorf("--gcp-cloud-run-scale-down-wait-time must be a whole number of milliseconds") + } details := map[string]any{ scalerKeyMinCount: f.min, scalerKeyMaxCount: f.max, scalerKeyInitialCount: f.initial, scalerKeyUtilizationTarget: f.utilization, + scalerKeyNoSyncQuietMs: f.scaleDownWait.Milliseconds(), } dc := converter.GetDefaultDataConverter() return dc.ToPayload(&details) @@ -1224,6 +1236,7 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) gcpScalerFlags() gcpScale max: c.GcpCloudRunMaxInstances, maxSet: f.Changed("gcp-cloud-run-max-instances"), initial: c.GcpCloudRunInitialInstances, initialSet: f.Changed("gcp-cloud-run-initial-instances"), utilization: c.GcpCloudRunUtilizationTarget, utilizationSet: f.Changed("gcp-cloud-run-utilization-target"), + scaleDownWait: c.GcpCloudRunScaleDownWaitTime.Duration(), scaleDownWaitSet: f.Changed("gcp-cloud-run-scale-down-wait-time"), } } @@ -1306,6 +1319,7 @@ func (c *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand) gcpScalerFla max: c.GcpCloudRunMaxInstances, maxSet: f.Changed("gcp-cloud-run-max-instances"), initial: c.GcpCloudRunInitialInstances, initialSet: f.Changed("gcp-cloud-run-initial-instances"), utilization: c.GcpCloudRunUtilizationTarget, utilizationSet: f.Changed("gcp-cloud-run-utilization-target"), + scaleDownWait: c.GcpCloudRunScaleDownWaitTime.Duration(), scaleDownWaitSet: f.Changed("gcp-cloud-run-scale-down-wait-time"), } } diff --git a/internal/temporalcli/commands.worker.deployment.internal_test.go b/internal/temporalcli/commands.worker.deployment.internal_test.go index 9db39ad77..09254df8d 100644 --- a/internal/temporalcli/commands.worker.deployment.internal_test.go +++ b/internal/temporalcli/commands.worker.deployment.internal_test.go @@ -2,6 +2,7 @@ package temporalcli import ( "testing" + "time" "github.com/stretchr/testify/require" computepb "go.temporal.io/api/compute/v1" @@ -44,46 +45,101 @@ func TestScalerTypeByProviderCoversAllProviders(t *testing.T) { } func TestGCPCloudRunScalerDetails(t *testing.T) { + // A fully-set, valid group; each case clones this and overrides one field so + // the all-or-none check passes and the case isolates a single value check. + valid := func() gcpScalerFlags { + return gcpScalerFlags{ + min: 1, minSet: true, + max: 10, maxSet: true, + initial: 5, initialSet: true, + utilization: 0.5, utilizationSet: true, + scaleDownWait: 90 * time.Second, scaleDownWaitSet: true, + } + } + // Nothing set -> nil payload so WCI defaults apply (min 0, max 30, - // initial 0, utilization_target 0.8). + // initial 0, utilization_target 0.8, no_sync_quiet_ms 90000). p, err := gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{}) require.NoError(t, err) require.Nil(t, p) - // Any scaler flag alongside a non-GCP provider is rejected. Covers both an - // instance-count flag and the utilization flag. + // Any scaler flag alongside a non-GCP provider is rejected. Covers an + // instance-count flag, the utilization flag, and the no-sync flag. _, err = gcpCloudRunScalerDetails("aws-lambda", gcpScalerFlags{minSet: true}) require.ErrorContains(t, err, "only valid with --gcp-cloud-run-worker-pool") _, err = gcpCloudRunScalerDetails("aws-lambda", gcpScalerFlags{utilization: 0.5, utilizationSet: true}) require.ErrorContains(t, err, "only valid with --gcp-cloud-run-worker-pool") + _, err = gcpCloudRunScalerDetails("aws-lambda", gcpScalerFlags{scaleDownWait: time.Second, scaleDownWaitSet: true}) + require.ErrorContains(t, err, "only valid with --gcp-cloud-run-worker-pool") - // All four settings are one all-or-none group: any partial set is rejected. + // All five settings are one all-or-none group: any partial set is rejected. _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 5, minSet: true}) require.ErrorContains(t, err, "must be set together") _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{utilization: 0.5, utilizationSet: true}) // utilization alone require.ErrorContains(t, err, "must be set together") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{ // trio set, utilization missing - min: 1, minSet: true, max: 3, maxSet: true, initial: 2, initialSet: true, - }) + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{scaleDownWait: time.Second, scaleDownWaitSet: true}) // wait-time alone require.ErrorContains(t, err, "must be set together") + // The four instance/utilization flags without scale-down-wait-time are also + // rejected: scale-down-wait-time is part of the same all-or-none group. + missingQuiet := valid() + missingQuiet.scaleDownWait, missingQuiet.scaleDownWaitSet = 0, false + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", missingQuiet) + require.ErrorContains(t, err, "must be set together") + + // Value checks, with the whole group set so the group check passes first. + neg := valid() + neg.min, neg.initial = -1, 0 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", neg) + require.ErrorContains(t, err, "--gcp-cloud-run-min-instances cannot be negative") - // Value checks, with all four set so the group check passes first. - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: -1, minSet: true, max: 3, maxSet: true, initial: 0, initialSet: true, utilization: 0.5, utilizationSet: true}) - require.ErrorContains(t, err, "cannot be negative") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 0, minSet: true, max: 0, maxSet: true, initial: 0, initialSet: true, utilization: 0.5, utilizationSet: true}) + maxTooLow := valid() + maxTooLow.min, maxTooLow.max, maxTooLow.initial = 0, 0, 0 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", maxTooLow) require.ErrorContains(t, err, "--gcp-cloud-run-max-instances must be at least 1") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 5, minSet: true, max: 3, maxSet: true, initial: 4, initialSet: true, utilization: 0.5, utilizationSet: true}) + + minGtMax := valid() + minGtMax.min, minGtMax.max, minGtMax.initial = 5, 3, 4 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", minGtMax) require.ErrorContains(t, err, "cannot exceed") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 2, minSet: true, max: 10, maxSet: true, initial: 15, initialSet: true, utilization: 0.5, utilizationSet: true}) + + initialOOR := valid() + initialOOR.min, initialOOR.max, initialOOR.initial = 2, 10, 15 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", initialOOR) require.ErrorContains(t, err, "must be between") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 0, minSet: true, max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 0, utilizationSet: true}) + + utilZero := valid() + utilZero.utilization = 0 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", utilZero) require.ErrorContains(t, err, "must be greater than 0 and at most 1") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 0, minSet: true, max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 1.5, utilizationSet: true}) + + utilHigh := valid() + utilHigh.utilization = 1.5 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", utilHigh) require.ErrorContains(t, err, "must be greater than 0 and at most 1") - // All four set and valid -> payload decodes to the WCI rate-based keys. + negQuiet := valid() + negQuiet.scaleDownWait = -time.Second + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", negQuiet) + require.ErrorContains(t, err, "--gcp-cloud-run-scale-down-wait-time cannot be negative") + + // A negative sub-millisecond value must be caught before Milliseconds() + // truncates it toward zero (which would send 0 and silently disable the wait). + negSubMs := valid() + negSubMs.scaleDownWait = -time.Microsecond + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", negSubMs) + require.ErrorContains(t, err, "--gcp-cloud-run-scale-down-wait-time cannot be negative") + + // A positive sub-millisecond value is rejected rather than silently rounded. + subMs := valid() + subMs.scaleDownWait = 500 * time.Microsecond + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", subMs) + require.ErrorContains(t, err, "--gcp-cloud-run-scale-down-wait-time must be a whole number of milliseconds") + + // Whole group set and valid -> payload decodes to the WCI rate-based keys. // JSON round-trips numbers as float64; WCI handles that on read. - p, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 1, minSet: true, max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 0.5, utilizationSet: true}) + ok := valid() + ok.scaleDownWait = 120 * time.Second + p, err = gcpCloudRunScalerDetails("gcp-cloud-run", ok) require.NoError(t, err) require.NotNil(t, p) var details map[string]any @@ -92,6 +148,7 @@ func TestGCPCloudRunScalerDetails(t *testing.T) { require.Equal(t, float64(10), details[scalerKeyMaxCount]) require.Equal(t, float64(5), details[scalerKeyInitialCount]) require.Equal(t, float64(0.5), details[scalerKeyUtilizationTarget]) + require.Equal(t, float64(120000), details[scalerKeyNoSyncQuietMs]) } func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { @@ -101,6 +158,7 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 0.75, utilizationSet: true, + scaleDownWait: 120 * time.Second, scaleDownWaitSet: true, }) require.NoError(t, err) require.NotNil(t, scalerDetails) @@ -114,7 +172,7 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { }, } - // JSON/structured path surfaces min, max, initial, and utilization. + // JSON/structured path surfaces min, max, initial, utilization, and scale-down-wait-time-ms. formatted := formatComputeConfigProto(cc) require.NotNil(t, formatted) sg, ok := formatted.ScalingGroups["default"] @@ -125,13 +183,15 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { require.NotNil(t, sg.Scaler.MaxInstances) require.NotNil(t, sg.Scaler.InitialInstances) require.NotNil(t, sg.Scaler.UtilizationTarget) + require.NotNil(t, sg.Scaler.ScaleDownWaitTimeMs) require.Equal(t, int64(0), *sg.Scaler.MinInstances) require.Equal(t, int64(10), *sg.Scaler.MaxInstances) require.Equal(t, int64(5), *sg.Scaler.InitialInstances) require.Equal(t, float64(0.75), *sg.Scaler.UtilizationTarget) + require.Equal(t, int64(120000), *sg.Scaler.ScaleDownWaitTimeMs) - // Human-readable summary reflects the settings (min, initial, max, utilization). - require.Equal(t, "gcp-cloud-run (min 0, initial 5, max 10, utilization 0.75)", computeConfigSummaryStr(cc)) + // Human-readable summary reflects the settings (min, initial, max, utilization, scale-down-wait-time-ms). + require.Equal(t, "gcp-cloud-run (min 0, initial 5, max 10, utilization 0.75, scale-down-wait-time-ms 120000)", computeConfigSummaryStr(cc)) // Without scaler details, the settings are nil and the summary is just the // provider (guards against printing zeroed-out values). @@ -150,5 +210,6 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { require.Nil(t, sg.Scaler.MaxInstances) require.Nil(t, sg.Scaler.InitialInstances) require.Nil(t, sg.Scaler.UtilizationTarget) + require.Nil(t, sg.Scaler.ScaleDownWaitTimeMs) require.Equal(t, "gcp-cloud-run", computeConfigSummaryStr(ccNoBounds)) } diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index 1eca97f6f..f63e195e1 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -1470,7 +1470,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { s.Error(res.Err) s.ErrorContains(res.Err, "only valid with --gcp-cloud-run-worker-pool") - // A lone flag is rejected: all four scaling settings must be set together. + // A lone flag is rejected: all five scaling settings must be set together. res = s.Execute( "worker", "deployment", "create-version", "--address", s.Address(), @@ -1503,7 +1503,86 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { s.Error(res.Err) s.ErrorContains(res.Err, "must be set together") - // min cannot exceed max (all four set so the group check passes first). + // The instance counts and utilization-target without scale-down-wait-time are + // also rejected: scale-down-wait-time is part of the same all-or-none group. + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "1", + "--gcp-cloud-run-max-instances", "3", + "--gcp-cloud-run-initial-instances", "2", + "--gcp-cloud-run-utilization-target", "0.5", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "must be set together") + + // scale-down-wait-time cannot be negative (all five set so the group check + // passes first). + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "0", + "--gcp-cloud-run-max-instances", "10", + "--gcp-cloud-run-initial-instances", "5", + "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time=-1s", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--gcp-cloud-run-scale-down-wait-time cannot be negative") + + // A negative sub-millisecond value is also rejected: it must be caught before + // the duration is truncated to whole milliseconds (which would send 0 and + // silently disable the wait instead). + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "0", + "--gcp-cloud-run-max-instances", "10", + "--gcp-cloud-run-initial-instances", "5", + "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time=-1us", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--gcp-cloud-run-scale-down-wait-time cannot be negative") + + // A positive sub-millisecond value is rejected rather than silently rounded. + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "0", + "--gcp-cloud-run-max-instances", "10", + "--gcp-cloud-run-initial-instances", "5", + "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time", "500us", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--gcp-cloud-run-scale-down-wait-time must be a whole number of milliseconds") + + // min cannot exceed max (all five set so the group check passes first). res = s.Execute( "worker", "deployment", "create-version", "--address", s.Address(), @@ -1517,6 +1596,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "3", "--gcp-cloud-run-initial-instances", "4", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "cannot exceed") @@ -1535,6 +1615,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "0", "--gcp-cloud-run-initial-instances", "0", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "--gcp-cloud-run-max-instances must be at least 1") @@ -1553,6 +1634,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "10", "--gcp-cloud-run-initial-instances", "15", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "must be between") @@ -1572,6 +1654,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "10", "--gcp-cloud-run-initial-instances", "5", "--gcp-cloud-run-utilization-target", "1.5", + "--gcp-cloud-run-scale-down-wait-time", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "must be greater than 0 and at most 1") @@ -1640,7 +1723,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat serviceAccount := "customer-sa@my-gcp-project.iam.gserviceaccount.com" // Scaler-only update (no provider flags): the mask is just scaler.details, - // no provider is sent, and all four settings are carried. + // no provider is sent, and all five settings are carried. res := s.Execute( "worker", "deployment", "update-version-compute-config", "--address", s.Address(), @@ -1649,6 +1732,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat "--gcp-cloud-run-max-instances", "10", "--gcp-cloud-run-initial-instances", "5", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-wait-time", "2m", ) s.NoError(res.Err) req := takeCaptured() @@ -1663,6 +1747,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat s.Equal(float64(10), details["max_count"]) s.Equal(float64(5), details["initial_count"]) s.Equal(float64(0.5), details["utilization_target"]) + s.Equal(float64(120000), details["no_sync_quiet_ms"]) // Switching to AWS Lambda clears the (rate-based) scaler.details so they // don't linger under the no-sync scaler. @@ -1700,7 +1785,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat s.NotContains(sg.GetUpdateMask().GetPaths(), "scaler.details") s.Equal("gcp-cloud-run", sg.GetScalingGroup().GetProvider().GetType()) - // A scaler-only update still requires all four flags together. + // A scaler-only update still requires all five flags together. res = s.Execute( "worker", "deployment", "update-version-compute-config", "--address", s.Address(), diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 85a2c67b3..1b5be42f4 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -1265,7 +1265,8 @@ commands: --gcp-cloud-run-min-instances 1 \ --gcp-cloud-run-max-instances 3 \ --gcp-cloud-run-initial-instances 1 \ - --gcp-cloud-run-utilization-target 0.75 + --gcp-cloud-run-utilization-target 0.75 \ + --gcp-cloud-run-scale-down-wait-time 5m ``` If a Worker Deployment Version with the supplied BuildID already exists, @@ -1332,24 +1333,27 @@ commands: description: | Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Defaults + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool. - name: gcp-cloud-run-max-instances type: int description: | Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Defaults + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. Defaults to 30 when unset. Only valid with --gcp-cloud-run-worker-pool. - name: gcp-cloud-run-initial-instances type: int description: | Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together, and this + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together, and this value must be between the min and max (inclusive). Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool. - name: gcp-cloud-run-utilization-target @@ -1357,10 +1361,23 @@ commands: description: | Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Lower + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. Lower values keep more spare capacity per worker. Defaults to 0.8 when unset. Only valid with --gcp-cloud-run-worker-pool. + - name: gcp-cloud-run-scale-down-wait-time + type: duration + description: | + Duration the scaler waits after it last saw unmet task demand + before it may scale the Cloud Run worker pool down. Raise this to + keep the pool from scaling down before long-running or bursty + activities finish. Optional, but --gcp-cloud-run-min-instances, + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. A + value of 0s disables the wait. Defaults to 90s when unset. Only + valid with --gcp-cloud-run-worker-pool. - name: temporal worker deployment describe-version summary: Show properties of a Worker Deployment Version @@ -1602,12 +1619,13 @@ commands: --gcp-cloud-run-min-instances 1 \ --gcp-cloud-run-max-instances 3 \ --gcp-cloud-run-initial-instances 1 \ - --gcp-cloud-run-utilization-target 0.75 + --gcp-cloud-run-utilization-target 0.75 \ + --gcp-cloud-run-scale-down-wait-time 5m ``` To update only the scaling settings on an existing GCP Cloud Run Worker - Deployment Version, supply the four scaler flags without the provider - fields (all four must be set together): + Deployment Version, supply the five scaler flags without the provider + fields (all five must be set together): ``` temporal worker deployment update-version-compute-config \ @@ -1615,7 +1633,8 @@ commands: --gcp-cloud-run-min-instances 1 \ --gcp-cloud-run-max-instances 3 \ --gcp-cloud-run-initial-instances 1 \ - --gcp-cloud-run-utilization-target 0.75 + --gcp-cloud-run-utilization-target 0.75 \ + --gcp-cloud-run-scale-down-wait-time 5m ``` Provider fields are only required when changing the compute provider. @@ -1692,39 +1711,56 @@ commands: description: | Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. If + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. If omitted, the version's existing scaling settings are left unchanged. - Only valid with --gcp-cloud-run-worker-pool. + Only applies to a GCP Cloud Run worker pool. - name: gcp-cloud-run-max-instances type: int description: | Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. If + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. If omitted, the version's existing scaling settings are left unchanged. - Only valid with --gcp-cloud-run-worker-pool. + Only applies to a GCP Cloud Run worker pool. - name: gcp-cloud-run-initial-instances type: int description: | Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together, and this + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together, and this value must be between the min and max (inclusive). If omitted, the - version's existing scaling settings are left unchanged. Only valid - with --gcp-cloud-run-worker-pool. + version's existing scaling settings are left unchanged. Only applies + to a GCP Cloud Run worker pool. - name: gcp-cloud-run-utilization-target type: float description: | Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Lower + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. Lower values keep more spare capacity per worker. If omitted, the version's - existing scaling settings are left unchanged. Only valid with - --gcp-cloud-run-worker-pool. + existing scaling settings are left unchanged. Only applies to a + GCP Cloud Run worker pool. + - name: gcp-cloud-run-scale-down-wait-time + type: duration + description: | + Duration the scaler waits after it last saw unmet task demand + before it may scale the Cloud Run worker pool down. Raise this to + keep the pool from scaling down before long-running or bursty + activities finish. Optional, but --gcp-cloud-run-min-instances, + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-wait-time must all be set together. A + value of 0s disables the wait. If omitted, the version's existing + scaling settings are left unchanged. Only applies to a + GCP Cloud Run worker pool. - name: remove type: bool description: |