Skip to content

Commit

Permalink
adds scaling properties to appengine standard version (GoogleCloudPla…
Browse files Browse the repository at this point in the history
…tform#3373)

* adds scaling properties to appengine standard version

* use optional scaling properties

as it defaults to automaticScaling

* cover all automatic_scaling properties during test

* update test for standard appengine version
  • Loading branch information
wvanderdeijl authored and Nathan Klish committed May 18, 2020
1 parent 3780f09 commit efc14cf
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 7 deletions.
106 changes: 100 additions & 6 deletions products/appengine/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ objects:
name: 'StandardAppVersion'
description: |
Standard App Version resource to create a new version of standard GAE Application.
Learn about the differences between the standard environment and the flexible environment
at https://cloud.google.com/appengine/docs/the-appengine-environments.
Currently supporting Zip and File Containers.
Currently does not support async operation checking.
collection_url_key: 'versions'
base_url: 'apps/{{project}}/services/{{service}}/versions'
delete_url: 'apps/{{project}}/services/{{service}}/versions/{{version_id}}'
Expand Down Expand Up @@ -401,8 +402,8 @@ objects:
name: 'deployment'
description: |
Code and application artifacts that make up this version.
required: false
properties:
required: true
properties:
- !ruby/object:Api::Type::NestedObject
name: 'zip'
description: 'Zip File'
Expand Down Expand Up @@ -457,8 +458,98 @@ objects:
name: 'instanceClass'
description: |
Instance class that is used to run this version. Valid values are
AutomaticScaling F1, F2, F4, F4_1G
(Only AutomaticScaling is supported at the moment)
AutomaticScaling: F1, F2, F4, F4_1G
BasicScaling or ManualScaling: B1, B2, B4, B4_1G, B8
Defaults to F1 for AutomaticScaling and B2 for ManualScaling and BasicScaling. If no scaling is specified, AutomaticScaling is chosen.
- !ruby/object:Api::Type::NestedObject
name: 'automaticScaling'
description: |
Automatic scaling is based on request rate, response latencies, and other application metrics.
conflicts:
- basicScaling
- manualScaling
properties:
- !ruby/object:Api::Type::Integer
name: 'maxConcurrentRequests'
description: |
Number of concurrent requests an automatic scaling instance can accept before the scheduler spawns a new instance.
Defaults to a runtime-specific value.
- !ruby/object:Api::Type::Integer
name: 'maxIdleInstances'
description: |
Maximum number of idle instances that should be maintained for this version.
- !ruby/object:Api::Type::String
name: 'maxPendingLatency'
description: |
Maximum amount of time that a request should wait in the pending queue before starting a new instance to handle it.
A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s".
- !ruby/object:Api::Type::Integer
name: 'minIdleInstances'
description: |
Minimum number of idle instances that should be maintained for this version. Only applicable for the default version of a service.
- !ruby/object:Api::Type::String
name: 'minPendingLatency'
description: |
Minimum amount of time a request should wait in the pending queue before starting a new instance to handle it.
A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s".
- !ruby/object:Api::Type::NestedObject
name: 'standardSchedulerSettings'
description: |
Scheduler settings for standard environment.
properties:
- !ruby/object:Api::Type::Double
name: 'targetCpuUtilization'
description: |
Target CPU utilization ratio to maintain when scaling. Should be a value in the range [0.50, 0.95], zero, or a negative value.
- !ruby/object:Api::Type::Double
name: 'targetThroughputUtilization'
description: |
Target throughput utilization ratio to maintain when scaling. Should be a value in the range [0.50, 0.95], zero, or a negative value.
- !ruby/object:Api::Type::Integer
name: 'minInstances'
description: |
Minimum number of instances to run for this version. Set to zero to disable minInstances configuration.
- !ruby/object:Api::Type::Integer
name: 'maxInstances'
description: |
Maximum number of instances to run for this version. Set to zero to disable maxInstances configuration.
- !ruby/object:Api::Type::NestedObject
name: 'basicScaling'
description: |
Basic scaling creates instances when your application receives requests. Each instance will be shut down when the application becomes idle. Basic scaling is ideal for work that is intermittent or driven by user activity.
conflicts:
- automaticScaling
- manualScaling
properties:
- !ruby/object:Api::Type::String
name: 'idleTimeout'
default_value: 900s
description: |
Duration of time after the last request that an instance must wait before the instance is shut down.
A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s". Defaults to 900s.
- !ruby/object:Api::Type::Integer
name: 'maxInstances'
required: true
description: |
Maximum number of instances to create for this version. Must be in the range [1.0, 200.0].
- !ruby/object:Api::Type::NestedObject
name: 'manualScaling'
description: |
A service with manual scaling runs continuously, allowing you to perform complex initialization and rely on the state of its memory over time.
conflicts:
- automaticScaling
- basicScaling
properties:
- !ruby/object:Api::Type::Integer
name: 'instances'
required: true
description: |
Number of instances to assign to the service at the start.
**Note:** When managing the number of instances at runtime through the App Engine Admin API or the (now deprecated) Python 2
Modules API set_num_instances() you must use `lifecycle.ignore_changes = ["manual_scaling"[0].instances]` to prevent drift detection.
# StandardAppVersion and FlexibleAppVersion use the same API endpoint (apps.services.versions)
# They are split apart as some of the fields will are necessary for one and not the other, and
# other fields may have different defaults. However, some fields are the same. If fixing a bug
Expand Down Expand Up @@ -1100,7 +1191,10 @@ objects:
name: 'instances'
required: true
description: |
Number of instances to assign to the service at the start. This number can later be altered by using the Modules API set_num_instances() function.
Number of instances to assign to the service at the start.
**Note:** When managing the number of instances at runtime through the App Engine Admin API or the (now deprecated) Python 2
Modules API set_num_instances() you must use `lifecycle.ignore_changes = ["manual_scaling"[0].instances]` to prevent drift detection.
- !ruby/object:Api::Resource
name: 'ApplicationUrlDispatchRules'
description: |
Expand Down
3 changes: 2 additions & 1 deletion products/appengine/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ overrides: !ruby/object:Overrides::ResourceOverrides
ignore_read: true
threadsafe: !ruby/object:Overrides::Terraform::PropertyOverride
ignore_read: true
# instanceClass defaults to a value based on the scaling method
instanceClass: !ruby/object:Overrides::Terraform::PropertyOverride
ignore_read: true
default_from_api: true
examples:
- !ruby/object:Provider::Terraform::Examples
name: "app_engine_standard_app_version"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ resource "google_app_engine_standard_app_version" "<%= ctx[:primary_resource_id]
port = "8080"
}

automatic_scaling {
max_concurrent_requests = 10
min_idle_instances = 1
max_idle_instances = 3
min_pending_latency = "1s"
max_pending_latency = "5s"
standard_scheduler_settings {
target_cpu_utilization = 0.5
target_throughput_utilization = 0.75
min_instances = 2
max_instances = 10
}
}

delete_service_on_destroy = true
}

Expand All @@ -39,6 +53,10 @@ resource "google_app_engine_standard_app_version" "myapp_v2" {
port = "8080"
}

basic_scaling {
max_instances = 5
}

noop_on_destroy = true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package google

import (
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"testing"
)

func TestAccAppEngineStandardAppVersion_update(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"org_id": getTestOrgFromEnv(t),
"billing_account": getTestBillingAccountFromEnv(t),
"random_suffix": randString(t, 10),
}

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAppEngineStandardAppVersionDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccAppEngineStandardAppVersion_python(context),
},
{
ResourceName: "google_app_engine_standard_app_version.foo",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"env_variables", "deployment", "entrypoint", "service", "noop_on_destroy"},
},
{
Config: testAccAppEngineStandardAppVersion_pythonUpdate(context),
},
{
ResourceName: "google_app_engine_standard_app_version.foo",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"env_variables", "deployment", "entrypoint", "service", "noop_on_destroy"},
},
},
})
}

func testAccAppEngineStandardAppVersion_python(context map[string]interface{}) string {
return Nprintf(`
resource "google_project" "my_project" {
name = "tf-test-appeng-std%{random_suffix}"
project_id = "tf-test-appeng-std%{random_suffix}"
org_id = "%{org_id}"
billing_account = "%{billing_account}"
}
resource "google_app_engine_application" "app" {
project = google_project.my_project.project_id
location_id = "us-central"
}
resource "google_project_service" "project" {
project = google_project.my_project.project_id
service = "appengine.googleapis.com"
disable_dependent_services = false
}
resource "google_app_engine_standard_app_version" "foo" {
project = google_project_service.project.project
version_id = "v1"
service = "default"
runtime = "python38"
entrypoint {
shell = "gunicorn -b :$PORT main:app"
}
deployment {
files {
name = "main.py"
source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/${google_storage_bucket_object.main.name}"
}
files {
name = "requirements.txt"
source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/${google_storage_bucket_object.requirements.name}"
}
}
env_variables = {
port = "8000"
}
instance_class = "F2"
automatic_scaling {
max_concurrent_requests = 10
min_idle_instances = 1
max_idle_instances = 3
min_pending_latency = "1s"
max_pending_latency = "5s"
standard_scheduler_settings {
target_cpu_utilization = 0.5
target_throughput_utilization = 0.75
min_instances = 2
max_instances = 10
}
}
noop_on_destroy = true
}
resource "google_storage_bucket" "bucket" {
project = google_project.my_project.project_id
name = "tf-test-%{random_suffix}-standard-ae-bucket"
}
resource "google_storage_bucket_object" "requirements" {
name = "requirements.txt"
bucket = google_storage_bucket.bucket.name
source = "./test-fixtures/appengine/hello-world-flask/requirements.txt"
}
resource "google_storage_bucket_object" "main" {
name = "main.py"
bucket = google_storage_bucket.bucket.name
source = "./test-fixtures/appengine/hello-world-flask/main.py"
}`, context)
}

func testAccAppEngineStandardAppVersion_pythonUpdate(context map[string]interface{}) string {
return Nprintf(`
resource "google_project" "my_project" {
name = "tf-test-appeng-std%{random_suffix}"
project_id = "tf-test-appeng-std%{random_suffix}"
org_id = "%{org_id}"
billing_account = "%{billing_account}"
}
resource "google_app_engine_application" "app" {
project = google_project.my_project.project_id
location_id = "us-central"
}
resource "google_project_service" "project" {
project = google_project.my_project.project_id
service = "appengine.googleapis.com"
disable_dependent_services = false
}
resource "google_app_engine_standard_app_version" "foo" {
project = google_project_service.project.project
version_id = "v1"
service = "default"
runtime = "python38"
entrypoint {
shell = "gunicorn -b :$PORT main:app"
}
deployment {
files {
name = "main.py"
source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/${google_storage_bucket_object.main.name}"
}
files {
name = "requirements.txt"
source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/${google_storage_bucket_object.requirements.name}"
}
}
env_variables = {
port = "8000"
}
instance_class = "B2"
basic_scaling {
max_instances = 5
}
noop_on_destroy = true
}
resource "google_storage_bucket" "bucket" {
project = google_project.my_project.project_id
name = "tf-test-%{random_suffix}-standard-ae-bucket"
}
resource "google_storage_bucket_object" "requirements" {
name = "requirements.txt"
bucket = google_storage_bucket.bucket.name
source = "./test-fixtures/appengine/hello-world-flask/requirements.txt"
}
resource "google_storage_bucket_object" "main" {
name = "main.py"
bucket = google_storage_bucket.bucket.name
source = "./test-fixtures/appengine/hello-world-flask/main.py"
}`, context)
}

0 comments on commit efc14cf

Please sign in to comment.