Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App Engine Service Split Traffic resource #2269

Merged
merged 33 commits into from
Feb 20, 2020

Conversation

khanali21
Copy link
Contributor

@khanali21 khanali21 commented Aug 28, 2019

  • Added custom_create in custom_code.
  • Added appengine service resource

Release Note for Downstream PRs (will be copied)

`google_app_engine_service_split_traffic`

@modular-magician
Copy link
Collaborator

Hello! I am a robot who works on Magic Modules PRs.

I have detected that you are a community contributor, so your PR will be assigned to someone with a commit-bit on this repo for initial review. They will authorize it to run through our CI pipeline, which will generate downstream PRs.

Thanks for your contribution! A human will be with you soon.

@emilymye, please review this PR or find an appropriate assignee.

@khanali21
Copy link
Contributor Author

@rileykarson I noticed that there is no option for the custom_create code in the generator. I thought it fitting to add this. This helps in Appengine Service Resource which does not have Create method.

If custom_create was not deliberately added, then I can move the resource under third_party as the custom resource.

Thanks
Ali

@khanali21
Copy link
Contributor Author

Example template to follow.

@rileykarson rileykarson self-requested a review August 28, 2019 23:03
@rileykarson
Copy link
Member

For Terraform, I think we want to expose this under another name than Service. Since a service can't be (directly) created, I think it would be confusing to have a google_app_engine_service resource defined that depended on a version creating the service.

As a user, I'd expect my config to look like:

resource "google_app_engine_service" "frontend" {
  id = "my-frontend-service"
  
} 

resource "google_app_engine_standard_app_version" "frontend_v2" {
  version_id = "v2"
  service = google_app_engine_service.frontend.id

  ...
}

When in reality, it's more likely to be like this, because the dependency is flipped:

resource "google_app_engine_standard_app_version" "frontend_v2" {
  version_id = "v2"
  service = "my-frontend-service"

  ...
}

resource "google_app_engine_service" "frontend" {
  id = google_app_engine_standard_version.frontend_v2.service
  
} 

Instead, I'd lean towards exposing the resource as google_app_engine_service_traffic_split. I believe we can override the name value in terraform.yaml to accomplish that, but if not we'll need to add a duplicate entry in api.yaml.

Copy link
Member

@rileykarson rileykarson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like StandardVersion, this is going to end up a bit of an atypical resource. Per my comment above, I think we should focus on the traffic splitting scenario since it's the only configurable portion of a service, and we should make this resource apply the specified traffic split on Create as well as Update.

name: 'split'
description: |
Mapping that defines fractional HTTP traffic diversion to different versions within the service.
required: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
required: false
required: true

Since the resource doesn't do anything else, I'm incline to make the traffic split required: true

@@ -73,24 +73,58 @@ objects:
For example, an application that handles customer requests might include separate services to handle tasks such as backend data analysis or API requests from mobile devices.
Each service has a collection of versions that define a specific set of code used to implement the functionality of that service.
base_url: 'apps/{{project}}/services'
self_link: 'apps/{{project}}/services/{{id}}'
update_url: 'apps/{{project}}/services/{{service_id}}?migrateTraffic=False&updateMask=split'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
update_url: 'apps/{{project}}/services/{{service_id}}?migrateTraffic=False&updateMask=split'
update_url: 'apps/{{project}}/services/{{service_id}}?migrateTraffic={{migrateTraffic}}'

We can expose migrateTraffic as configurable. If you add it to parameters: (instead of properties:), and make it url_param_only: true in terraform.yaml, this should work as intended.

The update mask should be appended automatically by setting update_mask: true.

# This code replaces the entire create method. Since the create
# method's function header can't be changed, the template
# inserts that for you - do not include it in your custom code.
attr_reader :custom_create
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your question, this not existing was just a matter of not needing it yet.

import_format: ["apps/{{project}}/services/{{service_id}}"]
mutex: "apps/{{project}}/services/{{service_id}}"
custom_code: !ruby/object:Provider::Terraform::CustomCode
custom_create: templates/terraform/custom_create/noop_on_create_appengine_version.go.erb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing nothing on update, what if we set the traffic split on create as well? This would mirror the IAM Policy resources that immediately set their values on create.

Setting immediately on singleton resources like this is a little unfortunate- Terraform won't show the old values in plan. On the other hand, it means that creating the resource actually does something, and the resource won't effectively always permadiff. IMO that matches to our expectations better, especially given the precedent in IAM.

To accomplish this, you can set the create_url to the same value as the update_url and create_verb to PATCH in api.yaml.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this may require using a custom encoder to add an update mask on create, since we haven't added a resource doing an HTTP PATCH on Terraform Create action before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether I need to add create_encoder in this case. So there are two things:

  1. Having updateMask in the create_url, I think this can be handled by just adding the constant like:
    create_url: 'apps/{{project}}/services/{{service_id}}?{{migrateTraffic}}&updateMask=split'
  1. Identifying if we need to add shardBy also in the updateMask (I am looking into it)

required: false
properties:
- !ruby/object:Api::Type::Enum
name: 'shardBy'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is one of these values automatically selected by the API? Or does it actually return UNSPECIFIED?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to test it shortly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you get a chance to test?

- :IP
- :RANDOM
- !ruby/object:Api::Type::KeyValuePairs
name: 'allocations'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels required: true. Did you find it wasn't the case?

@khanali21
Copy link
Contributor Author

@rileykarson Struggling with few things:

  1. The create_verb can only be PUT and POST. Do you think I should modify ruby code to allow PATCH as the create_verb too.
  2. The update_mask true includes the id field as well, however I want only one field "split" to be part of the updateMask, i.e. updateMask=split
  3. I don't see the need for overriding name, as I changed the name of the resource and then also updated the resourceref for the Version resource. Let me know if there is any other need for name override. BTW I could not find how override the name, I did find legacy_name but could not make it work as intended. Can you tell me if that is the right one.

thanks

@khanali21 khanali21 changed the title App Engine Service resource App Engine Service Split Traffic resource Sep 4, 2019
@khanali21
Copy link
Contributor Author

@rileykarson Kindly review this. I have made all the required changes, One question is still open.
Q. Should I just override the delete with no op as there is no deletion applicable in this resource.

@khanali21
Copy link
Contributor Author

resource "google_storage_bucket" "bucket" {
	name = "<%= ctx[:vars]['bucket_name'] %>"
}

resource "google_storage_bucket_object" "object" {
	name   = "hello-world.zip"
	bucket = "${google_storage_bucket.bucket.name}"
	source = "./test-fixtures/appengine/hello-world.zip"
}

resource "google_app_engine_standard_app_version" "myapp_v1" {
  version_id = "v1"
  service = "myapp2"
  runtime = "nodejs10"
  noop_on_destroy = true
  entrypoint {
    shell = "node ./app.js"
  }
  deployment {
    zip {
      source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/hello-world.zip"
    }  
  }
  env_variables = {
    port = "8080"
  } 

}
resource "google_app_engine_standard_app_version" "myapp_v2" {
  version_id = "v2"
  service = "myapp2"
  runtime = "nodejs10"
  noop_on_destroy = true
  entrypoint {
    shell = "node ./app.js"
  }
  deployment {
    zip {
      source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/hello-world.zip"
    }  
  }
  env_variables = {
    port = "8080"
  } 

}

resource "google_app_engine_service_split_traffic" "myapp2" {
  service_id = "${google_app_engine_standard_app_version.myapp_v2.service}"
  migrate_traffic = false
  split {
    shard_by = "IP"
    allocations = {
      v1 = 0.75
      v2 = 0.25
    }
  }
}

Copy link
Member

@rileykarson rileykarson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry! I had some other work to get done yesterday and didn't have time to make a review pass.

Per your questions;

  1. Yep, adding it was correct. I'm not sure why we restrict the list of verbs allowed.
  2. If you make the id / service (renamed based on my comment below) field url_param_only, it will be excluded from the update_mask.
  3. Sorry! I changed my mind a little since before, do you actually mind defining it as an entirely separate resource in api.yaml?
  4. Sounds good to me- I don't think there's a meaningful deletion state for a service's traffic, so deletion doing nothing makes the most sense.

@@ -67,30 +67,74 @@ objects:
- ALLOW
- DENY
- !ruby/object:Api::Resource
name: 'Service'
name: 'ServiceSplitTraffic'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of renaming Service, would you mind duplicating this into a separate resource definition? It feels a little silly, but it's the best way to represent fine-grained resources right now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also require exclusions in Ansible / InSpec for the Magician to work.

properties:
- !ruby/object:Api::Type::String
name: 'name'
output: true
description: |
Full path to the Service resource in the API. Example apps/myapp/services/default.
This field is used in responses only. Any value specified here in a request is ignored.
- !ruby/object:Api::Type::String
name: 'id'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have some liberty in terms of what we can call this field because it doesn't actually correspond to any fields we care about in the API. What do you think of service instead of id/service_id?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt here?

@khanali21
Copy link
Contributor Author

khanali21 commented Sep 11, 2019

@rileykarson Please review now.

Also here is my loud thinking on this:

Use case 1:
a) Enable appengine using google_app_engine_application resource
b) Create a new service named e.g. "myapp" and upload the code as v1 using google_app_engine_standard_app_version resource. (Please note that the service is automatically created if it is not existing). Use the noop_on_destroy to ensure that this version does not get deleted.
c) Create another version v2 for service myapp using google_app_engine_standard_app_version resource.
d) Split the traffic using the google_app_engine_service_split_traffic resource.

Problem: In this use case terraform destroy will leave the service myapp and version v1 when destroying all the resources.

Solution: Use the google_app_engine_service resource to delete the final version. For this we shall need the google_app_engine_service resource as I did in the first iteration which will ignore the create but allow deletion.

@khanali21
Copy link
Contributor Author

@rileykarson Can you please review this. And give your comments.

@modular-magician
Copy link
Collaborator

Hi! I'm the modular magician, I work on Magic Modules.
This PR seems not to have generated downstream PRs before, as of b044582.

Pull request statuses

No diff detected in Ansible.
No diff detected in Inspec.

New Pull Requests

I built this PR into one or more new PRs on other repositories, and when those are closed, this PR will also be merged and closed.
depends: hashicorp/terraform-provider-google-beta#1135
depends: GoogleCloudPlatform/terraform-google-conversion#199
depends: hashicorp/terraform-provider-google#4451

Copy link
Member

@rileykarson rileykarson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of an approach that requires adding a google_app_engine_service resource that's only really used for deletion, I'd prefer we add delete_service_on_destroy to google_app_engine_standard_app_version. That boolean would be mutually exclusive with noop_on_destroy, and would delete the service at the same time as that version.

Terraform will process deletion of versions in parallel for most configs; that means that the version that deletes the parent may delete it before the other version is "deleted" by Terraform. That'll be fine, though, deletion succeeds if the parent / resource isn't found.

properties:
- !ruby/object:Api::Type::String
name: 'name'
output: true
description: |
Full path to the Service resource in the API. Example apps/myapp/services/default.
This field is used in responses only. Any value specified here in a request is ignored.
- !ruby/object:Api::Type::String
name: 'id'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt here?

required: false
properties:
- !ruby/object:Api::Type::Enum
name: 'shardBy'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you get a chance to test?

@modular-magician
Copy link
Collaborator

Hi! I'm the modular magician, I work on Magic Modules.
I see that this PR has already had some downstream PRs generated. Any open downstreams are already updated to your most recent commit, b044582.

Pull request statuses

terraform-provider-google-beta already has an open PR.
terraform-google-conversion already has an open PR.
terraform-provider-google already has an open PR.
No diff detected in Ansible.
No diff detected in Inspec.

New Pull Requests

I didn't open any new pull requests because of this PR.

@@ -96,6 +96,10 @@ class CustomCode < Api::Object
# method's function header can't be changed, the template
# inserts that for you - do not include it in your custom code.
attr_reader :custom_delete
# This code replaces the entire create method. Since the create
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we're not using this, do you mind removing it + the changes in resource.erb? I'd rather not add unused code.

@modular-magician
Copy link
Collaborator

Hi! I'm the modular magician. Your PR generated some diffs in downstreams - here they are.

Diff report:

Terraform GA: Diff ( 20 files changed, 697 insertions(+), 94 deletions(-))
Terraform Beta: Diff ( 22 files changed, 705 insertions(+), 113 deletions(-))
TF Conversion: Diff ( 1 file changed, 101 insertions(+))

@khanali21
Copy link
Contributor Author

@rileykarson DONE :) Had a bad time with this one.... even the changes from resource.erb were not going away git acting up with resolving merge conflict.

@modular-magician
Copy link
Collaborator

Hi! I'm the modular magician. Your PR generated some diffs in downstreams - here they are.

Diff report:

Terraform GA: Diff ( 5 files changed, 647 insertions(+), 2 deletions(-))
Terraform Beta: Diff ( 5 files changed, 647 insertions(+), 2 deletions(-))
TF Conversion: Diff ( 1 file changed, 101 insertions(+))

Copy link
Member

@rileykarson rileykarson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for working through me on the drawn out process to get this resource added! Since AppEngine is one of GCP's oldest APIs, it doesn't tend to fit the model of API behaviour we built MM with, or the model of declarative resources that's evolved since AppEngine's creation.

@rileykarson rileykarson merged commit a9cabe8 into GoogleCloudPlatform:master Feb 20, 2020
nathkn pushed a commit to nathkn/magic-modules that referenced this pull request May 18, 2020
…#2269)

* Appengine Service resource

* Added custom_create in custom_code.
* Added appengine service resource

* Updates based on review

* Updates based on review

* update based on review

* Updates

* Update

* Update

* Updated the example

* Updated the example and website link

* Skip Delete on test and noop

* Rebased and merged the appengine terraform.yaml

* Updates

* Fixes

* updated the example and excluded split from reading

* updated the example

* Reverted custom_create related code

* Reverted resource.erb

* updates

* removed the error check

* Updates

* Fixes

* Fixed the import in test script

* Updates as per review

* chanded the operation to OpAsync

* Updated the Objects from Async to OpAsync

* Fixed the example primary resource name

* Testing by removing id_format and import_format

* reverted import and id format and also the example

* Fixed the id_format

* Updates based on review

* Typo fix

* Changed url_param_only to api_name

* Updated the resource.erb from upstream/master
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants